Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permalinks should be able to decode and encode via servers. #300

Merged
merged 9 commits into from
Mar 19, 2023
43 changes: 18 additions & 25 deletions src/helpers/Permalinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
*/
export interface PermalinkParts {
/**
* The room ID or alias the permalink references. May be null.
* The room ID or alias the permalink references. May be undefined.
*/
roomIdOrAlias: string;

/**
* The user ID the permalink references. May be null.
* The user ID the permalink references. May be undefined.
*/
userId: string;

/**
* The event ID the permalink references. May be null.
* The event ID the permalink references. May be undefined.
*/
eventId: string;

/**
* The servers the permalink is routed through. May be null or empty.
* The servers the permalink is routed through. May be undefined or empty.
*/
viaServers: string[];
}
Expand All @@ -38,7 +38,7 @@ export class Permalinks {
private static encodeViaArgs(servers: string[]): string {
if (!servers || !servers.length) return "";

return `?via=${servers.join("via=")}`;
return `?via=${servers.join("&via=")}`;
}

/**
Expand Down Expand Up @@ -77,32 +77,25 @@ export class Permalinks {
* @returns {PermalinkParts} The parts of the permalink.
*/
public static parseUrl(matrixTo: string): PermalinkParts {
if (!matrixTo || !matrixTo.startsWith("https://matrix.to/#/")) {
const matrixToRegexp = /^https:\/\/matrix\.to\/#\/(?<entity>[^/?]+)\/?(?<eventId>[^?]+)?(?<query>\?[^]*)?$/;

const url = matrixToRegexp.exec(matrixTo)?.groups;
if (!url) {
throw new Error("Not a valid matrix.to URL");
}

const parts = matrixTo.substring("https://matrix.to/#/".length).split("/");

const entity = decodeURIComponent(parts[0]);
const entity = decodeURIComponent(url.entity);
if (entity[0] === '@') {
return { userId: entity, roomIdOrAlias: undefined, eventId: undefined, viaServers: undefined };
} else if (entity[0] === '#' || entity[0] === '!') {
if (parts.length === 1) {
return { roomIdOrAlias: entity, userId: undefined, eventId: undefined, viaServers: [] };
}

// rejoin the rest because v3 room can have slashes
const eventIdAndQuery = decodeURIComponent(parts.length > 1 ? parts.slice(1).join('/') : "");
const secondaryParts = eventIdAndQuery.split('?');

const eventId = secondaryParts[0];
const query = secondaryParts.length > 1 ? secondaryParts[1] : "";

const via = query.split("via=").filter(p => !!p);

return { roomIdOrAlias: entity, eventId: eventId, viaServers: via, userId: undefined };
return {
userId: undefined,
roomIdOrAlias: entity,
eventId: url.eventId && decodeURIComponent(url.eventId),
viaServers: new URLSearchParams(url.query).getAll('via'),
};
} else {
throw new Error("Unexpected entity");
}

throw new Error("Unexpected entity");
}
}
29 changes: 22 additions & 7 deletions test/helpers/PermalinksTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ describe('Permalinks', () => {
it('should generate a URL for a room ID with via', () => {
const roomId = "!test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomId}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomId}?via=${via.join("&via=")}`;
expect(Permalinks.forRoom(roomId, via)).toBe(expected);
});

it('should generate a URL for a room alias with via', () => {
const roomAlias = "#test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomAlias}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomAlias}?via=${via.join("&via=")}`;
expect(Permalinks.forRoom(roomAlias, via)).toBe(expected);
});
});
Expand All @@ -48,15 +48,15 @@ describe('Permalinks', () => {
const roomId = "!test:example.org";
const eventId = "$test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomId}/${eventId}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomId}/${eventId}?via=${via.join("&via=")}`;
expect(Permalinks.forEvent(roomId, eventId, via)).toBe(expected);
});

it('should generate a URL for an event ID with room alias with via', () => {
const roomAlias = "#test:example.org";
const eventId = "$test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomAlias}/${eventId}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomAlias}/${eventId}?via=${via.join("&via=")}`;
expect(Permalinks.forEvent(roomAlias, eventId, via)).toBe(expected);
});
});
Expand All @@ -81,6 +81,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${userId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forUser(userId))).toMatchObject(expected);
});

it('should parse room alias URLs', () => {
Expand All @@ -94,6 +95,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forRoom(roomId))).toMatchObject(expected);
});

it('should parse room ID URLs', () => {
Expand All @@ -107,6 +109,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forRoom(roomId))).toMatchObject(expected);
});

it('should parse room alias permalink URLs', () => {
Expand All @@ -116,6 +119,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId))).toMatchObject(expected);
});

it('should parse room ID permalink URLs', () => {
Expand All @@ -125,26 +129,37 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId))).toMatchObject(expected);
});

it('should parse room alias permalink URLs with via servers', () => {
const roomId = "#test:example.org";
const eventId = "$ev:example.org";
const via = ["one.example.org", "two.example.org"];
const expected: PermalinkParts = { userId: undefined, roomIdOrAlias: roomId, viaServers: via, eventId };
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("via=")}`);

const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("&via=")}`);
expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId, via))).toMatchObject(expected);
});

it('should parse room ID permalink URLs with via servers', () => {
const roomId = "!test:example.org";
const eventId = "$ev:example.org";
const via = ["one.example.org", "two.example.org"];
const expected: PermalinkParts = { userId: undefined, roomIdOrAlias: roomId, viaServers: via, eventId };
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("via=")}`);
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("&via=")}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId, via))).toMatchObject(expected);
});

it('should parse room ID URLs with via servers', () => {
const roomId = "!example:example.org";
const via = ["example.org"];
const expected: PermalinkParts = { userId: undefined, roomIdOrAlias: roomId, viaServers: via, eventId: undefined };
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/?via=${via.join("&via=")}`);
expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forRoom(roomId, via))).toMatchObject(expected);
});
});
});