Merge pull request #464 from vector-im/bwindels/well-known
Add .well-known support
This commit is contained in:
commit
7946a3e4d7
14 changed files with 131 additions and 58 deletions
|
@ -105,7 +105,7 @@ export class RootViewModel extends ViewModel {
|
||||||
_showLogin(loginToken) {
|
_showLogin(loginToken) {
|
||||||
this._setSection(() => {
|
this._setSection(() => {
|
||||||
this._loginViewModel = new LoginViewModel(this.childOptions({
|
this._loginViewModel = new LoginViewModel(this.childOptions({
|
||||||
defaultHomeServer: this.platform.config["defaultHomeServer"],
|
defaultHomeserver: this.platform.config["defaultHomeServer"],
|
||||||
createSessionContainer: this._createSessionContainer,
|
createSessionContainer: this._createSessionContainer,
|
||||||
ready: sessionContainer => {
|
ready: sessionContainer => {
|
||||||
// we don't want to load the session container again,
|
// we don't want to load the session container again,
|
||||||
|
|
|
@ -46,7 +46,7 @@ export class CompleteSSOLoginViewModel extends ViewModel {
|
||||||
const homeserver = await this.platform.settingsStorage.getString("sso_ongoing_login_homeserver");
|
const homeserver = await this.platform.settingsStorage.getString("sso_ongoing_login_homeserver");
|
||||||
let loginOptions;
|
let loginOptions;
|
||||||
try {
|
try {
|
||||||
loginOptions = await this._sessionContainer.queryLogin(homeserver);
|
loginOptions = await this._sessionContainer.queryLogin(homeserver).result;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
this._showError(err.message);
|
this._showError(err.message);
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {SessionLoadViewModel} from "../SessionLoadViewModel.js";
|
||||||
export class LoginViewModel extends ViewModel {
|
export class LoginViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {ready, defaultHomeServer, createSessionContainer, loginToken} = options;
|
const {ready, defaultHomeserver, createSessionContainer, loginToken} = options;
|
||||||
this._createSessionContainer = createSessionContainer;
|
this._createSessionContainer = createSessionContainer;
|
||||||
this._ready = ready;
|
this._ready = ready;
|
||||||
this._loginToken = loginToken;
|
this._loginToken = loginToken;
|
||||||
|
@ -35,7 +35,8 @@ export class LoginViewModel extends ViewModel {
|
||||||
this._completeSSOLoginViewModel = null;
|
this._completeSSOLoginViewModel = null;
|
||||||
this._loadViewModel = null;
|
this._loadViewModel = null;
|
||||||
this._loadViewModelSubscription = null;
|
this._loadViewModelSubscription = null;
|
||||||
this._homeserver = defaultHomeServer;
|
this._homeserver = defaultHomeserver;
|
||||||
|
this._queriedHomeserver = null;
|
||||||
this._errorMessage = "";
|
this._errorMessage = "";
|
||||||
this._hideHomeserver = false;
|
this._hideHomeserver = false;
|
||||||
this._isBusy = false;
|
this._isBusy = false;
|
||||||
|
@ -48,6 +49,7 @@ export class LoginViewModel extends ViewModel {
|
||||||
get startSSOLoginViewModel() { return this._startSSOLoginViewModel; }
|
get startSSOLoginViewModel() { return this._startSSOLoginViewModel; }
|
||||||
get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; }
|
get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; }
|
||||||
get homeserver() { return this._homeserver; }
|
get homeserver() { return this._homeserver; }
|
||||||
|
get resolvedHomeserver() { return this._loginOptions?.homeserver; }
|
||||||
get errorMessage() { return this._errorMessage; }
|
get errorMessage() { return this._errorMessage; }
|
||||||
get showHomeserver() { return !this._hideHomeserver; }
|
get showHomeserver() { return !this._hideHomeserver; }
|
||||||
get loadViewModel() {return this._loadViewModel; }
|
get loadViewModel() {return this._loadViewModel; }
|
||||||
|
@ -71,7 +73,7 @@ export class LoginViewModel extends ViewModel {
|
||||||
this.emitChange("completeSSOLoginViewModel");
|
this.emitChange("completeSSOLoginViewModel");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await this.queryHomeServer();
|
await this.queryHomeserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,13 +158,18 @@ export class LoginViewModel extends ViewModel {
|
||||||
this.emitChange("disposeViewModels");
|
this.emitChange("disposeViewModels");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setHomeServer(newHomeserver) {
|
async setHomeserver(newHomeserver) {
|
||||||
this._homeserver = newHomeserver;
|
this._homeserver = newHomeserver;
|
||||||
// abort ongoing query, if any
|
// clear everything set by queryHomeserver
|
||||||
|
this._loginOptions = null;
|
||||||
|
this._queriedHomeserver = null;
|
||||||
|
this._showError("");
|
||||||
|
this._disposeViewModels();
|
||||||
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
|
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
|
||||||
this.emitChange("isFetchingLoginOptions");
|
this.emitChange(); // multiple fields changing
|
||||||
|
// also clear the timeout if it is still running
|
||||||
this.disposeTracked(this._abortHomeserverQueryTimeout);
|
this.disposeTracked(this._abortHomeserverQueryTimeout);
|
||||||
const timeout = this.clock.createTimeout(2000);
|
const timeout = this.clock.createTimeout(1000);
|
||||||
this._abortHomeserverQueryTimeout = this.track(() => timeout.abort());
|
this._abortHomeserverQueryTimeout = this.track(() => timeout.abort());
|
||||||
try {
|
try {
|
||||||
await timeout.elapsed();
|
await timeout.elapsed();
|
||||||
|
@ -174,22 +181,30 @@ export class LoginViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout);
|
this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout);
|
||||||
this.queryHomeServer();
|
this.queryHomeserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryHomeServer() {
|
async queryHomeserver() {
|
||||||
this._errorMessage = "";
|
// don't repeat a query we've just done
|
||||||
this.emitChange("errorMessage");
|
if (this._homeserver === this._queriedHomeserver || this._homeserver === "") {
|
||||||
// if query is called before the typing timeout hits (e.g. field lost focus), cancel the timeout so we don't query again.
|
return;
|
||||||
|
}
|
||||||
|
this._queriedHomeserver = this._homeserver;
|
||||||
|
// given that setHomeserver already clears everything set here,
|
||||||
|
// and that is the only way to change the homeserver,
|
||||||
|
// we don't need to reset things again here.
|
||||||
|
// However, clear things set by setHomeserver:
|
||||||
|
// if query is called before the typing timeout hits (e.g. field lost focus),
|
||||||
|
// cancel the timeout so we don't query again.
|
||||||
this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout);
|
this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout);
|
||||||
// cancel ongoing query operation, if any
|
// cancel ongoing query operation, if any
|
||||||
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
|
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
|
||||||
this._disposeViewModels();
|
|
||||||
try {
|
try {
|
||||||
const queryOperation = this._sessionContainer.queryLogin(this._homeserver);
|
const queryOperation = this._sessionContainer.queryLogin(this._homeserver);
|
||||||
this._abortQueryOperation = this.track(() => queryOperation.abort());
|
this._abortQueryOperation = this.track(() => queryOperation.abort());
|
||||||
this.emitChange("isFetchingLoginOptions");
|
this.emitChange("isFetchingLoginOptions");
|
||||||
this._loginOptions = await queryOperation.result;
|
this._loginOptions = await queryOperation.result;
|
||||||
|
this.emitChange("resolvedHomeserver");
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (e.name === "AbortError") {
|
if (e.name === "AbortError") {
|
||||||
|
@ -209,7 +224,7 @@ export class LoginViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this._showError("Could not query login methods supported by the homeserver");
|
this._showError(`Could not query login methods supported by ${this.homeserver}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,15 +43,14 @@ export class PasswordLoginViewModel extends ViewModel {
|
||||||
async login(username, password) {
|
async login(username, password) {
|
||||||
this._errorMessage = "";
|
this._errorMessage = "";
|
||||||
this.emitChange("errorMessage");
|
this.emitChange("errorMessage");
|
||||||
const loginMethod = this._loginOptions.password(username, password);
|
const status = await this._attemptLogin(this._loginOptions.password(username, password));
|
||||||
const status = await this._attemptLogin(loginMethod);
|
|
||||||
let error = "";
|
let error = "";
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case LoginFailure.Credentials:
|
case LoginFailure.Credentials:
|
||||||
error = this.i18n`Your username and/or password don't seem to be correct.`;
|
error = this.i18n`Your username and/or password don't seem to be correct.`;
|
||||||
break;
|
break;
|
||||||
case LoginFailure.Connection:
|
case LoginFailure.Connection:
|
||||||
error = this.i18n`Can't connect to ${loginMethod.homeServer}.`;
|
error = this.i18n`Can't connect to ${this._loginOptions.homeserver}.`;
|
||||||
break;
|
break;
|
||||||
case LoginFailure.Unknown:
|
case LoginFailure.Unknown:
|
||||||
error = this.i18n`Something went wrong while checking your login and password.`;
|
error = this.i18n`Something went wrong while checking your login and password.`;
|
||||||
|
|
|
@ -151,7 +151,7 @@ export class SettingsViewModel extends ViewModel {
|
||||||
this.pushNotifications.enabledOnServer = null;
|
this.pushNotifications.enabledOnServer = null;
|
||||||
this.pushNotifications.serverError = null;
|
this.pushNotifications.serverError = null;
|
||||||
try {
|
try {
|
||||||
this.pushNotifications.enabledOnServer = await this._session.checkPusherEnabledOnHomeServer();
|
this.pushNotifications.enabledOnServer = await this._session.checkPusherEnabledOnHomeserver();
|
||||||
this.emitChange("pushNotifications.enabledOnServer");
|
this.emitChange("pushNotifications.enabledOnServer");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.pushNotifications.serverError = err;
|
this.pushNotifications.serverError = err;
|
||||||
|
|
|
@ -46,7 +46,7 @@ const PICKLE_KEY = "DEFAULT_KEY";
|
||||||
const PUSHER_KEY = "pusher";
|
const PUSHER_KEY = "pusher";
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
// sessionInfo contains deviceId, userId and homeServer
|
// sessionInfo contains deviceId, userId and homeserver
|
||||||
constructor({storage, hsApi, sessionInfo, olm, olmWorker, platform, mediaRepository}) {
|
constructor({storage, hsApi, sessionInfo, olm, olmWorker, platform, mediaRepository}) {
|
||||||
this._platform = platform;
|
this._platform = platform;
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
|
@ -636,7 +636,7 @@ export class Session {
|
||||||
return !!pusherData;
|
return !!pusherData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkPusherEnabledOnHomeServer() {
|
async checkPusherEnabledOnHomeserver() {
|
||||||
const readTxn = await this._storage.readTxn([this._storage.storeNames.session]);
|
const readTxn = await this._storage.readTxn([this._storage.storeNames.session]);
|
||||||
const pusherData = await readTxn.session.get(PUSHER_KEY);
|
const pusherData = await readTxn.session.get(PUSHER_KEY);
|
||||||
if (!pusherData) {
|
if (!pusherData) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createEnum} from "../utils/enum.js";
|
import {createEnum} from "../utils/enum.js";
|
||||||
|
import {lookupHomeserver} from "./well-known.js";
|
||||||
import {AbortableOperation} from "../utils/AbortableOperation";
|
import {AbortableOperation} from "../utils/AbortableOperation";
|
||||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
import {ObservableValue} from "../observable/ObservableValue.js";
|
||||||
import {HomeServerApi} from "./net/HomeServerApi.js";
|
import {HomeServerApi} from "./net/HomeServerApi.js";
|
||||||
|
@ -28,14 +30,6 @@ import {PasswordLoginMethod} from "./login/PasswordLoginMethod.js";
|
||||||
import {TokenLoginMethod} from "./login/TokenLoginMethod.js";
|
import {TokenLoginMethod} from "./login/TokenLoginMethod.js";
|
||||||
import {SSOLoginHelper} from "./login/SSOLoginHelper.js";
|
import {SSOLoginHelper} from "./login/SSOLoginHelper.js";
|
||||||
|
|
||||||
function normalizeHomeserver(homeServer) {
|
|
||||||
try {
|
|
||||||
return new URL(homeServer).origin;
|
|
||||||
} catch (err) {
|
|
||||||
return new URL(`https://${homeServer}`).origin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadStatus = createEnum(
|
export const LoadStatus = createEnum(
|
||||||
"NotLoading",
|
"NotLoading",
|
||||||
"Login",
|
"Login",
|
||||||
|
@ -54,7 +48,6 @@ export const LoginFailure = createEnum(
|
||||||
"Unknown",
|
"Unknown",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
export class SessionContainer {
|
export class SessionContainer {
|
||||||
constructor({platform, olmPromise, workerPromise}) {
|
constructor({platform, olmPromise, workerPromise}) {
|
||||||
this._platform = platform;
|
this._platform = platform;
|
||||||
|
@ -102,33 +95,35 @@ export class SessionContainer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseLoginOptions(options, homeServer) {
|
_parseLoginOptions(options, homeserver) {
|
||||||
/*
|
/*
|
||||||
Take server response and return new object which has two props password and sso which
|
Take server response and return new object which has two props password and sso which
|
||||||
implements LoginMethod
|
implements LoginMethod
|
||||||
*/
|
*/
|
||||||
const flows = options.flows;
|
const flows = options.flows;
|
||||||
const result = {};
|
const result = {homeserver};
|
||||||
for (const flow of flows) {
|
for (const flow of flows) {
|
||||||
if (flow.type === "m.login.password") {
|
if (flow.type === "m.login.password") {
|
||||||
result.password = (username, password) => new PasswordLoginMethod({homeServer, username, password});
|
result.password = (username, password) => new PasswordLoginMethod({homeserver, username, password});
|
||||||
}
|
}
|
||||||
else if (flow.type === "m.login.sso" && flows.find(flow => flow.type === "m.login.token")) {
|
else if (flow.type === "m.login.sso" && flows.find(flow => flow.type === "m.login.token")) {
|
||||||
result.sso = new SSOLoginHelper(homeServer);
|
result.sso = new SSOLoginHelper(homeserver);
|
||||||
}
|
}
|
||||||
else if (flow.type === "m.login.token") {
|
else if (flow.type === "m.login.token") {
|
||||||
result.token = loginToken => new TokenLoginMethod({homeServer, loginToken});
|
result.token = loginToken => new TokenLoginMethod({homeserver, loginToken});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryLogin(homeServer) {
|
queryLogin(homeserver) {
|
||||||
const normalizedHS = normalizeHomeserver(homeServer);
|
|
||||||
const hsApi = new HomeServerApi({homeServer: normalizedHS, request: this._platform.request});
|
|
||||||
return new AbortableOperation(async setAbortable => {
|
return new AbortableOperation(async setAbortable => {
|
||||||
|
homeserver = await lookupHomeserver(homeserver, (url, options) => {
|
||||||
|
return setAbortable(this._platform.request(url, options));
|
||||||
|
});
|
||||||
|
const hsApi = new HomeServerApi({homeserver, request: this._platform.request});
|
||||||
const response = await setAbortable(hsApi.getLoginFlows()).response();
|
const response = await setAbortable(hsApi.getLoginFlows()).response();
|
||||||
return this._parseLoginOptions(response, normalizedHS);
|
return this._parseLoginOptions(response, homeserver);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,14 +141,15 @@ export class SessionContainer {
|
||||||
let sessionInfo;
|
let sessionInfo;
|
||||||
try {
|
try {
|
||||||
const request = this._platform.request;
|
const request = this._platform.request;
|
||||||
const hsApi = new HomeServerApi({homeServer: loginMethod.homeServer, request});
|
const hsApi = new HomeServerApi({homeserver: loginMethod.homeserver, request});
|
||||||
const loginData = await loginMethod.login(hsApi, "Hydrogen", log);
|
const loginData = await loginMethod.login(hsApi, "Hydrogen", log);
|
||||||
const sessionId = this.createNewSessionId();
|
const sessionId = this.createNewSessionId();
|
||||||
sessionInfo = {
|
sessionInfo = {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
deviceId: loginData.device_id,
|
deviceId: loginData.device_id,
|
||||||
userId: loginData.user_id,
|
userId: loginData.user_id,
|
||||||
homeServer: loginMethod.homeServer,
|
homeServer: loginMethod.homeserver, // deprecate this over time
|
||||||
|
homeserver: loginMethod.homeserver,
|
||||||
accessToken: loginData.access_token,
|
accessToken: loginData.access_token,
|
||||||
lastUsed: clock.now()
|
lastUsed: clock.now()
|
||||||
};
|
};
|
||||||
|
@ -202,7 +198,7 @@ export class SessionContainer {
|
||||||
createMeasure: clock.createMeasure
|
createMeasure: clock.createMeasure
|
||||||
});
|
});
|
||||||
const hsApi = new HomeServerApi({
|
const hsApi = new HomeServerApi({
|
||||||
homeServer: sessionInfo.homeServer,
|
homeserver: sessionInfo.homeServer,
|
||||||
accessToken: sessionInfo.accessToken,
|
accessToken: sessionInfo.accessToken,
|
||||||
request: this._platform.request,
|
request: this._platform.request,
|
||||||
reconnector: this._reconnector,
|
reconnector: this._reconnector,
|
||||||
|
@ -214,7 +210,7 @@ export class SessionContainer {
|
||||||
id: sessionInfo.id,
|
id: sessionInfo.id,
|
||||||
deviceId: sessionInfo.deviceId,
|
deviceId: sessionInfo.deviceId,
|
||||||
userId: sessionInfo.userId,
|
userId: sessionInfo.userId,
|
||||||
homeServer: sessionInfo.homeServer,
|
homeserver: sessionInfo.homeServer,
|
||||||
};
|
};
|
||||||
const olm = await this._olmPromise;
|
const olm = await this._olmPromise;
|
||||||
let olmWorker = null;
|
let olmWorker = null;
|
||||||
|
@ -224,7 +220,7 @@ export class SessionContainer {
|
||||||
this._requestScheduler = new RequestScheduler({hsApi, clock});
|
this._requestScheduler = new RequestScheduler({hsApi, clock});
|
||||||
this._requestScheduler.start();
|
this._requestScheduler.start();
|
||||||
const mediaRepository = new MediaRepository({
|
const mediaRepository = new MediaRepository({
|
||||||
homeServer: sessionInfo.homeServer,
|
homeserver: sessionInfo.homeServer,
|
||||||
platform: this._platform,
|
platform: this._platform,
|
||||||
});
|
});
|
||||||
this._session = new Session({
|
this._session = new Session({
|
||||||
|
|
|
@ -15,8 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class LoginMethod {
|
export class LoginMethod {
|
||||||
constructor({homeServer}) {
|
constructor({homeserver}) {
|
||||||
this.homeServer = homeServer;
|
this.homeserver = homeserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
|
|
@ -19,10 +19,10 @@ import {encodeQueryParams, encodeBody} from "./common.js";
|
||||||
import {HomeServerRequest} from "./HomeServerRequest.js";
|
import {HomeServerRequest} from "./HomeServerRequest.js";
|
||||||
|
|
||||||
export class HomeServerApi {
|
export class HomeServerApi {
|
||||||
constructor({homeServer, accessToken, request, reconnector}) {
|
constructor({homeserver, accessToken, request, reconnector}) {
|
||||||
// store these both in a closure somehow so it's harder to get at in case of XSS?
|
// store these both in a closure somehow so it's harder to get at in case of XSS?
|
||||||
// one could change the homeserver as well so the token gets sent there, so both must be protected from read/write
|
// one could change the homeserver as well so the token gets sent there, so both must be protected from read/write
|
||||||
this._homeserver = homeServer;
|
this._homeserver = homeserver;
|
||||||
this._accessToken = accessToken;
|
this._accessToken = accessToken;
|
||||||
this._requestFn = request;
|
this._requestFn = request;
|
||||||
this._reconnector = reconnector;
|
this._reconnector = reconnector;
|
||||||
|
@ -234,7 +234,7 @@ export function tests() {
|
||||||
"superficial happy path for GET": async assert => {
|
"superficial happy path for GET": async assert => {
|
||||||
const hsApi = new HomeServerApi({
|
const hsApi = new HomeServerApi({
|
||||||
request: () => new MockRequest().respond(200, 42),
|
request: () => new MockRequest().respond(200, 42),
|
||||||
homeServer: "https://hs.tld"
|
homeserver: "https://hs.tld"
|
||||||
});
|
});
|
||||||
const result = await hsApi._get("foo", null, null, null).response();
|
const result = await hsApi._get("foo", null, null, null).response();
|
||||||
assert.strictEqual(result, 42);
|
assert.strictEqual(result, 42);
|
||||||
|
|
|
@ -18,8 +18,8 @@ import {encodeQueryParams} from "./common.js";
|
||||||
import {decryptAttachment} from "../e2ee/attachment.js";
|
import {decryptAttachment} from "../e2ee/attachment.js";
|
||||||
|
|
||||||
export class MediaRepository {
|
export class MediaRepository {
|
||||||
constructor({homeServer, platform}) {
|
constructor({homeserver, platform}) {
|
||||||
this._homeServer = homeServer;
|
this._homeserver = homeserver;
|
||||||
this._platform = platform;
|
this._platform = platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export class MediaRepository {
|
||||||
const parts = this._parseMxcUrl(url);
|
const parts = this._parseMxcUrl(url);
|
||||||
if (parts) {
|
if (parts) {
|
||||||
const [serverName, mediaId] = parts;
|
const [serverName, mediaId] = parts;
|
||||||
const httpUrl = `${this._homeServer}/_matrix/media/r0/thumbnail/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
const httpUrl = `${this._homeserver}/_matrix/media/r0/thumbnail/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
||||||
return httpUrl + "?" + encodeQueryParams({width: Math.round(width), height: Math.round(height), method});
|
return httpUrl + "?" + encodeQueryParams({width: Math.round(width), height: Math.round(height), method});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -37,7 +37,7 @@ export class MediaRepository {
|
||||||
const parts = this._parseMxcUrl(url);
|
const parts = this._parseMxcUrl(url);
|
||||||
if (parts) {
|
if (parts) {
|
||||||
const [serverName, mediaId] = parts;
|
const [serverName, mediaId] = parts;
|
||||||
return `${this._homeServer}/_matrix/media/r0/download/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
return `${this._homeserver}/_matrix/media/r0/download/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
53
src/matrix/well-known.js
Normal file
53
src/matrix/well-known.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizeHomeserver(homeserver) {
|
||||||
|
try {
|
||||||
|
return new URL(homeserver).origin;
|
||||||
|
} catch (err) {
|
||||||
|
return new URL(`https://${homeserver}`).origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWellKnownResponse(homeserver, request) {
|
||||||
|
const requestOptions = {format: "json", timeout: 30000, method: "GET"};
|
||||||
|
try {
|
||||||
|
const wellKnownUrl = `${homeserver}/.well-known/matrix/client`;
|
||||||
|
return await request(wellKnownUrl, requestOptions).response();
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === "ConnectionError") {
|
||||||
|
// don't fail lookup on a ConnectionError,
|
||||||
|
// there might be a missing CORS header on a 404 response or something,
|
||||||
|
// which won't be a problem necessarily with homeserver requests later on ...
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function lookupHomeserver(homeserver, request) {
|
||||||
|
homeserver = normalizeHomeserver(homeserver);
|
||||||
|
const wellKnownResponse = await getWellKnownResponse(homeserver, request);
|
||||||
|
if (wellKnownResponse && wellKnownResponse.status === 200) {
|
||||||
|
const {body} = wellKnownResponse;
|
||||||
|
const wellKnownHomeserver = body["m.homeserver"]?.["base_url"];
|
||||||
|
if (typeof wellKnownHomeserver === "string") {
|
||||||
|
homeserver = normalizeHomeserver(wellKnownHomeserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return homeserver;
|
||||||
|
}
|
|
@ -238,6 +238,12 @@ a.button-action {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.LoginView_forwardInfo {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-left: 1em;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
.CompleteSSOView_title {
|
.CompleteSSOView_title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,13 @@ export class LoginView extends TemplateView {
|
||||||
placeholder: vm.i18n`Your matrix homeserver`,
|
placeholder: vm.i18n`Your matrix homeserver`,
|
||||||
value: vm.homeserver,
|
value: vm.homeserver,
|
||||||
disabled,
|
disabled,
|
||||||
onInput: () => vm.setHomeServer(event.target.value),
|
onInput: event => vm.setHomeserver(event.target.value),
|
||||||
onChange: event => vm.queryHomeServer(),
|
onChange: () => vm.queryHomeserver(),
|
||||||
}),
|
}),
|
||||||
|
t.p({className: {
|
||||||
|
LoginView_forwardInfo: true,
|
||||||
|
hidden: vm => !vm.resolvedHomeserver
|
||||||
|
}}, vm => vm.i18n`You will connect to ${vm.resolvedHomeserver}.`),
|
||||||
t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "error"}, vm.i18n(vm.errorMessage))),
|
t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "error"}, vm.i18n(vm.errorMessage))),
|
||||||
]
|
]
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<script id="main" type="module">
|
<script id="main" type="module">
|
||||||
import {LoginView} from "./login/LoginView.js";
|
import {LoginView} from "./login/LoginView.js";
|
||||||
const view = new LoginView(vm({
|
const view = new LoginView(vm({
|
||||||
defaultHomeServer: "https://hs.tld",
|
defaultHomeserver: "https://hs.tld",
|
||||||
login: () => alert("Logging in!"),
|
login: () => alert("Logging in!"),
|
||||||
cancelUrl: "#/session"
|
cancelUrl: "#/session"
|
||||||
}));
|
}));
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
loading: true,
|
loading: true,
|
||||||
}),
|
}),
|
||||||
cancelUrl: "#/session",
|
cancelUrl: "#/session",
|
||||||
defaultHomeServer: "https://hs.tld",
|
defaultHomeserver: "https://hs.tld",
|
||||||
}));
|
}));
|
||||||
document.getElementById("login-loading").appendChild(view.mount());
|
document.getElementById("login-loading").appendChild(view.mount());
|
||||||
</script>
|
</script>
|
||||||
|
|
Reference in a new issue