mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-10-31 08:15:07 +00:00
feat(permalinks): point Share links/QR at to.healthchat.ch
Add a config-driven matrix_to_url option so Share dialog links and QR codes resolve against a matrix.to-compatible resolver instead of the hardcoded public matrix.to. MatrixToPermalinkConstructor now reads the base URL lazily from config (default https://matrix.to), keeping the matrix.to URL format. HealthChat configs point it at the in-house fork https://to.healthchat.ch and drop the conflicting permalink_prefix (which routes to the Element-format constructor a matrix.to fork can't parse).
This commit is contained in:
@@ -33,7 +33,7 @@
|
|||||||
"preferred_domain": "jitsi.healthchat.ch"
|
"preferred_domain": "jitsi.healthchat.ch"
|
||||||
},
|
},
|
||||||
"logout_redirect_url": "",
|
"logout_redirect_url": "",
|
||||||
"permalink_prefix": "https://matrix.to",
|
"matrix_to_url": "https://to.healthchat.ch",
|
||||||
"room_directory": {
|
"room_directory": {
|
||||||
"servers": ["matrix.org"]
|
"servers": ["matrix.org"]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"preferred_domain": "jitsi.healthchat.ch"
|
"preferred_domain": "jitsi.healthchat.ch"
|
||||||
},
|
},
|
||||||
"logout_redirect_url": "",
|
"logout_redirect_url": "",
|
||||||
"permalink_prefix": "https://matrix.to",
|
"matrix_to_url": "https://to.healthchat.ch",
|
||||||
"room_directory": {
|
"room_directory": {
|
||||||
"servers": [
|
"servers": [
|
||||||
"matrix.org"
|
"matrix.org"
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ export interface IConfigOptions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
permalink_prefix?: string;
|
permalink_prefix?: string;
|
||||||
|
/**
|
||||||
|
* Base URL of a matrix.to-compatible resolver to use for Share links and QR codes,
|
||||||
|
* instead of the public `https://matrix.to`. Must use the matrix.to URL format
|
||||||
|
* (`/#/<entity>`), e.g. a self-hosted matrix.to fork.
|
||||||
|
*/
|
||||||
|
matrix_to_url?: string;
|
||||||
|
|
||||||
update_base_url?: string;
|
update_base_url?: string;
|
||||||
desktop_builds: {
|
desktop_builds: {
|
||||||
|
|||||||
@@ -7,10 +7,27 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
|
import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
|
||||||
|
import SdkConfig from "../../SdkConfig";
|
||||||
|
|
||||||
export const host = "matrix.to";
|
const DEFAULT_BASE_URL = "https://matrix.to";
|
||||||
export const baseUrl = `https://${host}`;
|
|
||||||
export const baseUrlPattern = `^(?:https?://)?${host.replace(".", "\\.")}/#/(.*)`;
|
/**
|
||||||
|
* The base URL of the matrix.to-compatible resolver to generate permalinks against.
|
||||||
|
* Defaults to the public `https://matrix.to`, but can be pointed at a self-hosted
|
||||||
|
* matrix.to fork via the `matrix_to_url` config option. Read lazily so the value
|
||||||
|
* reflects the runtime config rather than module-load time.
|
||||||
|
*/
|
||||||
|
export function baseUrl(): string {
|
||||||
|
return SdkConfig.get("matrix_to_url") ?? DEFAULT_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function host(): string {
|
||||||
|
return new URL(baseUrl()).host;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function baseUrlPattern(): string {
|
||||||
|
return `^(?:https?://)?${host().replaceAll(".", "\\.")}/#/(.*)`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates matrix.to permalinks
|
* Generates matrix.to permalinks
|
||||||
@@ -21,23 +38,23 @@ export default class MatrixToPermalinkConstructor extends PermalinkConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public forEvent(roomId: string, eventId: string, serverCandidates: string[]): string {
|
public forEvent(roomId: string, eventId: string, serverCandidates: string[]): string {
|
||||||
return `${baseUrl}/#/${roomId}/${eventId}${this.encodeServerCandidates(serverCandidates)}`;
|
return `${baseUrl()}/#/${roomId}/${eventId}${this.encodeServerCandidates(serverCandidates)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public forRoom(roomIdOrAlias: string, serverCandidates: string[]): string {
|
public forRoom(roomIdOrAlias: string, serverCandidates: string[]): string {
|
||||||
return `${baseUrl}/#/${roomIdOrAlias}${this.encodeServerCandidates(serverCandidates)}`;
|
return `${baseUrl()}/#/${roomIdOrAlias}${this.encodeServerCandidates(serverCandidates)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public forUser(userId: string): string {
|
public forUser(userId: string): string {
|
||||||
return `${baseUrl}/#/${userId}`;
|
return `${baseUrl()}/#/${userId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public forEntity(entityId: string): string {
|
public forEntity(entityId: string): string {
|
||||||
return `${baseUrl}/#/${entityId}`;
|
return `${baseUrl()}/#/${entityId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isPermalinkHost(testHost: string): boolean {
|
public isPermalinkHost(testHost: string): boolean {
|
||||||
return testHost === host;
|
return testHost === host();
|
||||||
}
|
}
|
||||||
|
|
||||||
public encodeServerCandidates(candidates: string[]): string {
|
public encodeServerCandidates(candidates: string[]): string {
|
||||||
@@ -52,7 +69,7 @@ export default class MatrixToPermalinkConstructor extends PermalinkConstructor {
|
|||||||
throw new Error("Does not appear to be a permalink");
|
throw new Error("Does not appear to be a permalink");
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches = [...fullUrl.matchAll(new RegExp(baseUrlPattern, "gi"))][0];
|
const matches = [...fullUrl.matchAll(new RegExp(baseUrlPattern(), "gi"))][0];
|
||||||
|
|
||||||
if (!matches || matches.length < 2) {
|
if (!matches || matches.length < 2) {
|
||||||
throw new Error("Does not appear to be a permalink");
|
throw new Error("Does not appear to be a permalink");
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import MatrixToPermalinkConstructor, {
|
|||||||
baseUrl as matrixtoBaseUrl,
|
baseUrl as matrixtoBaseUrl,
|
||||||
baseUrlPattern as matrixToBaseUrlPattern,
|
baseUrlPattern as matrixToBaseUrlPattern,
|
||||||
} from "./MatrixToPermalinkConstructor";
|
} from "./MatrixToPermalinkConstructor";
|
||||||
|
// `matrixtoBaseUrl` and `matrixToBaseUrlPattern` are functions (config-driven); call them at use sites.
|
||||||
import { type PermalinkParts } from "./PermalinkConstructor";
|
import { type PermalinkParts } from "./PermalinkConstructor";
|
||||||
import type PermalinkConstructor from "./PermalinkConstructor";
|
import type PermalinkConstructor from "./PermalinkConstructor";
|
||||||
import ElementPermalinkConstructor from "./ElementPermalinkConstructor";
|
import ElementPermalinkConstructor from "./ElementPermalinkConstructor";
|
||||||
@@ -342,13 +343,13 @@ export function tryTransformEntityToPermalink(matrixClient: MatrixClient, entity
|
|||||||
if (permalinkParts) {
|
if (permalinkParts) {
|
||||||
if (permalinkParts.roomIdOrAlias) {
|
if (permalinkParts.roomIdOrAlias) {
|
||||||
const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : "";
|
const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : "";
|
||||||
let pl = matrixtoBaseUrl + `/#/${permalinkParts.roomIdOrAlias}${eventIdPart}`;
|
let pl = matrixtoBaseUrl() + `/#/${permalinkParts.roomIdOrAlias}${eventIdPart}`;
|
||||||
if (permalinkParts.viaServers?.length) {
|
if (permalinkParts.viaServers?.length) {
|
||||||
pl += new MatrixToPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers);
|
pl += new MatrixToPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers);
|
||||||
}
|
}
|
||||||
return pl;
|
return pl;
|
||||||
} else if (permalinkParts.userId) {
|
} else if (permalinkParts.userId) {
|
||||||
return matrixtoBaseUrl + `/#/${permalinkParts.userId}`;
|
return matrixtoBaseUrl() + `/#/${permalinkParts.userId}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -437,7 +438,7 @@ export function getPrimaryPermalinkEntity(permalink: string): string | null {
|
|||||||
*/
|
*/
|
||||||
function getPermalinkConstructor(isPill = false): PermalinkConstructor {
|
function getPermalinkConstructor(isPill = false): PermalinkConstructor {
|
||||||
const elementPrefix = SdkConfig.get("permalink_prefix");
|
const elementPrefix = SdkConfig.get("permalink_prefix");
|
||||||
if (elementPrefix && elementPrefix !== matrixtoBaseUrl && !isPill) {
|
if (elementPrefix && elementPrefix !== matrixtoBaseUrl() && !isPill) {
|
||||||
return new ElementPermalinkConstructor(elementPrefix);
|
return new ElementPermalinkConstructor(elementPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +449,7 @@ export function parsePermalink(fullUrl: string): PermalinkParts | null {
|
|||||||
try {
|
try {
|
||||||
const elementPrefix = SdkConfig.get("permalink_prefix");
|
const elementPrefix = SdkConfig.get("permalink_prefix");
|
||||||
const decodedUrl = decodeURIComponent(fullUrl);
|
const decodedUrl = decodeURIComponent(fullUrl);
|
||||||
if (new RegExp(matrixToBaseUrlPattern, "i").test(decodedUrl)) {
|
if (new RegExp(matrixToBaseUrlPattern(), "i").test(decodedUrl)) {
|
||||||
return new MatrixToPermalinkConstructor().parsePermalink(decodedUrl);
|
return new MatrixToPermalinkConstructor().parsePermalink(decodedUrl);
|
||||||
} else if (fullUrl.startsWith("matrix:")) {
|
} else if (fullUrl.startsWith("matrix:")) {
|
||||||
return new MatrixSchemePermalinkConstructor().parsePermalink(fullUrl);
|
return new MatrixSchemePermalinkConstructor().parsePermalink(fullUrl);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import MatrixToPermalinkConstructor from "../../../../src/utils/permalinks/MatrixToPermalinkConstructor";
|
import MatrixToPermalinkConstructor from "../../../../src/utils/permalinks/MatrixToPermalinkConstructor";
|
||||||
import { PermalinkParts } from "../../../../src/utils/permalinks/PermalinkConstructor";
|
import { PermalinkParts } from "../../../../src/utils/permalinks/PermalinkConstructor";
|
||||||
|
import SdkConfig from "../../../../src/SdkConfig";
|
||||||
|
|
||||||
describe("MatrixToPermalinkConstructor", () => {
|
describe("MatrixToPermalinkConstructor", () => {
|
||||||
const peramlinkConstructor = new MatrixToPermalinkConstructor();
|
const peramlinkConstructor = new MatrixToPermalinkConstructor();
|
||||||
@@ -49,4 +50,32 @@ describe("MatrixToPermalinkConstructor", () => {
|
|||||||
).toEqual("https://matrix.to/#/!myroom:example.com/$event4?via=one.example.com&via=two.example.com");
|
).toEqual("https://matrix.to/#/!myroom:example.com/$event4?via=one.example.com&via=two.example.com");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("with a custom matrix_to_url config", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
SdkConfig.add({ matrix_to_url: "https://to.healthchat.ch" });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
SdkConfig.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates links against the configured host", () => {
|
||||||
|
const constructor = new MatrixToPermalinkConstructor();
|
||||||
|
expect(constructor.forUser("@user:example.com")).toEqual("https://to.healthchat.ch/#/@user:example.com");
|
||||||
|
expect(constructor.forRoom("!myroom:example.com", [])).toEqual(
|
||||||
|
"https://to.healthchat.ch/#/!myroom:example.com",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses links from the configured host and rejects matrix.to links", () => {
|
||||||
|
const constructor = new MatrixToPermalinkConstructor();
|
||||||
|
expect(constructor.parsePermalink("https://to.healthchat.ch/#/@user:example.com")).toEqual(
|
||||||
|
new PermalinkParts(null, null, "@user:example.com", null),
|
||||||
|
);
|
||||||
|
expect(() => constructor.parsePermalink("https://matrix.to/#/@user:example.com")).toThrow(
|
||||||
|
new Error("Does not appear to be a permalink"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ complete re-branding/private labeling, a more personalised experience can be ach
|
|||||||
3. `brand`: Optional name for the app. Defaults to `Element`. This is used throughout the application in various strings/locations.
|
3. `brand`: Optional name for the app. Defaults to `Element`. This is used throughout the application in various strings/locations.
|
||||||
4. `permalink_prefix`: An optional URL pointing to an Element Web deployment. For example, `https://app.element.io`. This will
|
4. `permalink_prefix`: An optional URL pointing to an Element Web deployment. For example, `https://app.element.io`. This will
|
||||||
change all permalinks (via the "Share" menus) to point at the Element Web deployment rather than `matrix.to`.
|
change all permalinks (via the "Share" menus) to point at the Element Web deployment rather than `matrix.to`.
|
||||||
|
4a. `matrix_to_url`: An optional base URL of a matrix.to-compatible resolver to use for Share links and QR codes instead of
|
||||||
|
the public `https://matrix.to`. Unlike `permalink_prefix` (which points at an Element Web deployment and uses Element's
|
||||||
|
`/#/room/…` URL format), this keeps the matrix.to URL format (`/#/<entity>`), so it must point at a matrix.to deployment or
|
||||||
|
fork. For example, `https://to.example.com`.
|
||||||
5. `desktop_builds`: Optional. Where the desktop builds for the application are, if available. This is explained in more detail
|
5. `desktop_builds`: Optional. Where the desktop builds for the application are, if available. This is explained in more detail
|
||||||
down below.
|
down below.
|
||||||
6. `mobile_builds`: Optional. Like `desktop_builds`, except for the mobile apps. Also described in more detail down below.
|
6. `mobile_builds`: Optional. Like `desktop_builds`, except for the mobile apps. Also described in more detail down below.
|
||||||
|
|||||||
Reference in New Issue
Block a user