Skip to content

Commit a1da347

Browse files
authored
Apply some code quality suggestions from SonarQube (#144)
* Apply some code quality suggestions from SonarQube * Prefer globalThis over window https://sonarcloud.io/organizations/matrix-org/rules?open=typescript%3AS7764&rule_key=typescript%3AS7764 * De-negate delayed event conditions * Apply prefer-readonly lint rule * Tag MSC-related identifiers as `@experimental` * Optimize expression - Use optional chaining - Don't call toString on a string * Apply await-thenable lint rule * Apply no-duplicate-imports lint rule * Remove useless assignment * Migrate from deprecated Jest methods * Fix tested delay parent ID arguments * Improve code coverage * Set `noUnusedLocals` TSConfig option * Remove overwritten assignment * Add code coverage for requests over postMessage * De-negate response/request check
1 parent 120a290 commit a1da347

File tree

11 files changed

+438
-262
lines changed

11 files changed

+438
-262
lines changed

.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
browser: true,
99
},
1010
rules: {
11+
"no-duplicate-imports": ["error"],
1112
"no-var": ["warn"],
1213
"prefer-rest-params": ["warn"],
1314
"prefer-spread": ["warn"],
@@ -24,6 +25,8 @@ module.exports = {
2425
asyncArrow: "always",
2526
},
2627
],
28+
"@typescript-eslint/await-thenable": ["error"],
29+
"@typescript-eslint/prefer-readonly": ["error"],
2730
"arrow-parens": "off",
2831
"prefer-promise-reject-errors": "off",
2932
"quotes": "off",

examples/widget/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
function parseFragment() {
18-
const fragmentString = window.location.hash || "?";
18+
const fragmentString = globalThis.location.hash || "?";
1919
return new URLSearchParams(fragmentString.substring(Math.max(fragmentString.indexOf("?"), 0)));
2020
}
2121

src/ClientWidgetApi.ts

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import {
6262
} from "./interfaces/SendToDeviceAction";
6363
import { EventDirection, EventKind, WidgetEventCapability } from "./models/WidgetEventCapability";
6464
import { IRoomEvent } from "./interfaces/IRoomEvent";
65-
import { IRoomAccountData } from "./interfaces/IRoomAccountData";
6665
import {
6766
IGetOpenIDActionRequest,
6867
IGetOpenIDActionResponseData,
@@ -141,15 +140,15 @@ export class ClientWidgetApi extends EventEmitter {
141140
private cachedWidgetVersions: ApiVersion[] | null = null;
142141
// contentLoadedActionSent is used to check that only one ContentLoaded request is send.
143142
private contentLoadedActionSent = false;
144-
private allowedCapabilities = new Set<Capability>();
145-
private allowedEvents: WidgetEventCapability[] = [];
143+
private readonly allowedCapabilities = new Set<Capability>();
144+
private readonly allowedEvents: WidgetEventCapability[] = [];
146145
private isStopped = false;
147146
private turnServers: AsyncGenerator<ITurnServer> | null = null;
148147
private contentLoadedWaitTimer?: ReturnType<typeof setTimeout>;
149148
// Stores pending requests to push a room's state to the widget
150-
private pushRoomStateTasks = new Set<Promise<void>>();
149+
private readonly pushRoomStateTasks = new Set<Promise<void>>();
151150
// Room ID → event type → state key → events to be pushed
152-
private pushRoomStateResult = new Map<string, Map<string, Map<string, IRoomEvent>>>();
151+
private readonly pushRoomStateResult = new Map<string, Map<string, Map<string, IRoomEvent>>>();
153152
private flushRoomStateTask: Promise<void> | null = null;
154153

155154
/**
@@ -162,8 +161,8 @@ export class ClientWidgetApi extends EventEmitter {
162161
*/
163162
public constructor(
164163
public readonly widget: Widget,
165-
private iframe: HTMLIFrameElement,
166-
private driver: WidgetDriver,
164+
iframe: HTMLIFrameElement,
165+
private readonly driver: WidgetDriver,
167166
) {
168167
super();
169168
if (!iframe?.contentWindow) {
@@ -175,7 +174,12 @@ export class ClientWidgetApi extends EventEmitter {
175174
if (!driver) {
176175
throw new Error("Invalid driver");
177176
}
178-
this.transport = new PostmessageTransport(WidgetApiDirection.ToWidget, widget.id, iframe.contentWindow, window);
177+
this.transport = new PostmessageTransport(
178+
WidgetApiDirection.ToWidget,
179+
widget.id,
180+
iframe.contentWindow,
181+
globalThis,
182+
);
179183
this.transport.targetOrigin = widget.origin;
180184
this.transport.on("message", this.handleMessage.bind(this));
181185

@@ -230,7 +234,7 @@ export class ClientWidgetApi extends EventEmitter {
230234

231235
public async getWidgetVersions(): Promise<ApiVersion[]> {
232236
if (Array.isArray(this.cachedWidgetVersions)) {
233-
return Promise.resolve(this.cachedWidgetVersions);
237+
return this.cachedWidgetVersions;
234238
}
235239

236240
try {
@@ -381,7 +385,7 @@ export class ClientWidgetApi extends EventEmitter {
381385
});
382386
}
383387

384-
if (!request.data?.uri || !request.data?.uri.toString().startsWith("https://matrix.to/#")) {
388+
if (!request.data?.uri.startsWith("https://matrix.to/#")) {
385389
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
386390
error: { message: "Invalid matrix.to URI" },
387391
});
@@ -467,9 +471,9 @@ export class ClientWidgetApi extends EventEmitter {
467471

468472
this.driver.askOpenID(observer);
469473
}
474+
470475
private handleReadRoomAccountData(request: IReadRoomAccountDataFromWidgetActionRequest): void | Promise<void> {
471-
let events: Promise<IRoomAccountData[]> = Promise.resolve([]);
472-
events = this.driver.readRoomAccountData(request.data.type);
476+
const events = this.driver.readRoomAccountData(request.data.type);
473477

474478
if (!this.canReceiveRoomAccountData(request.data.type)) {
475479
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
@@ -609,17 +613,17 @@ export class ClientWidgetApi extends EventEmitter {
609613
});
610614
}
611615

612-
if (!isDelayedEvent) {
613-
sendEventPromise = this.driver.sendEvent(
616+
if (isDelayedEvent) {
617+
sendEventPromise = this.driver.sendDelayedEvent(
618+
request.data.delay ?? null,
619+
request.data.parent_delay_id ?? null,
614620
request.data.type,
615621
request.data.content || {},
616622
request.data.state_key,
617623
request.data.room_id,
618624
);
619625
} else {
620-
sendEventPromise = this.driver.sendDelayedEvent(
621-
request.data.delay ?? null,
622-
request.data.parent_delay_id ?? null,
626+
sendEventPromise = this.driver.sendEvent(
623627
request.data.type,
624628
request.data.content || {},
625629
request.data.state_key,
@@ -635,17 +639,17 @@ export class ClientWidgetApi extends EventEmitter {
635639
});
636640
}
637641

638-
if (!isDelayedEvent) {
639-
sendEventPromise = this.driver.sendEvent(
642+
if (isDelayedEvent) {
643+
sendEventPromise = this.driver.sendDelayedEvent(
644+
request.data.delay ?? null,
645+
request.data.parent_delay_id ?? null,
640646
request.data.type,
641647
content,
642648
null, // not sending a state event
643649
request.data.room_id,
644650
);
645651
} else {
646-
sendEventPromise = this.driver.sendDelayedEvent(
647-
request.data.delay ?? null,
648-
request.data.parent_delay_id ?? null,
652+
sendEventPromise = this.driver.sendEvent(
649653
request.data.type,
650654
content,
651655
null, // not sending a state event
@@ -715,25 +719,25 @@ export class ClientWidgetApi extends EventEmitter {
715719

716720
private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise<void> {
717721
if (!request.data.type) {
718-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
722+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
719723
error: { message: "Invalid request - missing event type" },
720724
});
721725
} else if (!request.data.messages) {
722-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
726+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
723727
error: { message: "Invalid request - missing event contents" },
724728
});
725729
} else if (typeof request.data.encrypted !== "boolean") {
726-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
730+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
727731
error: { message: "Invalid request - missing encryption flag" },
728732
});
729733
} else if (!this.canSendToDeviceEvent(request.data.type)) {
730-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
734+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
731735
error: { message: "Cannot send to-device events of this type" },
732736
});
733737
} else {
734738
try {
735739
await this.driver.sendToDevice(request.data.type, request.data.encrypted, request.data.messages);
736-
await this.transport.reply<ISendToDeviceFromWidgetResponseData>(request, {});
740+
this.transport.reply<ISendToDeviceFromWidgetResponseData>(request, {});
737741
} catch (e) {
738742
console.error("error sending to-device event", e);
739743
this.handleDriverError(e, request, "Error sending event");
@@ -762,12 +766,12 @@ export class ClientWidgetApi extends EventEmitter {
762766

763767
private async handleWatchTurnServers(request: IWatchTurnServersRequest): Promise<void> {
764768
if (!this.hasCapability(MatrixCapabilities.MSC3846TurnServers)) {
765-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
769+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
766770
error: { message: "Missing capability" },
767771
});
768772
} else if (this.turnServers) {
769773
// We're already polling, so this is a no-op
770-
await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
774+
this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
771775
} else {
772776
try {
773777
const turnServers = this.driver.getTurnServers();
@@ -776,14 +780,14 @@ export class ClientWidgetApi extends EventEmitter {
776780
// client isn't banned from getting TURN servers entirely
777781
const { done, value } = await turnServers.next();
778782
if (done) throw new Error("Client refuses to provide any TURN servers");
779-
await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
783+
this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
780784

781785
// Start the poll loop, sending the widget the initial result
782786
this.pollTurnServers(turnServers, value);
783787
this.turnServers = turnServers;
784788
} catch (e) {
785789
console.error("error getting first TURN server results", e);
786-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
790+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
787791
error: { message: "TURN servers not available" },
788792
});
789793
}
@@ -792,17 +796,17 @@ export class ClientWidgetApi extends EventEmitter {
792796

793797
private async handleUnwatchTurnServers(request: IUnwatchTurnServersRequest): Promise<void> {
794798
if (!this.hasCapability(MatrixCapabilities.MSC3846TurnServers)) {
795-
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
799+
this.transport.reply<IWidgetApiErrorResponseData>(request, {
796800
error: { message: "Missing capability" },
797801
});
798802
} else if (!this.turnServers) {
799803
// We weren't polling anyways, so this is a no-op
800-
await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
804+
this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
801805
} else {
802806
// Stop the generator, allowing it to clean up
803807
await this.turnServers.return(undefined);
804808
this.turnServers = null;
805-
await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
809+
this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
806810
}
807811
}
808812

@@ -1147,7 +1151,7 @@ export class ClientWidgetApi extends EventEmitter {
11471151
private async flushRoomState(): Promise<void> {
11481152
try {
11491153
// Only send a single action once all concurrent tasks have completed
1150-
do await Promise.all([...this.pushRoomStateTasks]);
1154+
do await Promise.all(this.pushRoomStateTasks);
11511155
while (this.pushRoomStateTasks.size > 0);
11521156

11531157
const events: IRoomEvent[] = [];
@@ -1257,7 +1261,7 @@ export class ClientWidgetApi extends EventEmitter {
12571261
eventTypeMap.set(rawEvent.type, stateKeyMap);
12581262
}
12591263
if (!stateKeyMap.has(rawEvent.type)) stateKeyMap.set(rawEvent.state_key, rawEvent);
1260-
do await Promise.all([...this.pushRoomStateTasks]);
1264+
do await Promise.all(this.pushRoomStateTasks);
12611265
while (this.pushRoomStateTasks.size > 0);
12621266
await this.flushRoomStateTask;
12631267
}

src/WidgetApi.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class WidgetApi extends EventEmitter {
131131

132132
private capabilitiesFinished = false;
133133
private supportsMSC2974Renegotiate = false;
134-
private requestedCapabilities: Capability[] = [];
134+
private readonly requestedCapabilities: Capability[] = [];
135135
private approvedCapabilities?: Capability[];
136136
private cachedClientVersions?: ApiVersion[];
137137
private turnServerWatchers = 0;
@@ -142,15 +142,17 @@ export class WidgetApi extends EventEmitter {
142142
* the API will use the widget ID from the first valid request it receives.
143143
* @param {string} clientOrigin The origin of the client, or null if not known.
144144
*/
145-
public constructor(
146-
widgetId: string | null = null,
147-
private clientOrigin: string | null = null,
148-
) {
145+
public constructor(widgetId: string | null = null, clientOrigin: string | null = null) {
149146
super();
150-
if (!window.parent) {
147+
if (!globalThis.parent) {
151148
throw new Error("No parent window. This widget doesn't appear to be embedded properly.");
152149
}
153-
this.transport = new PostmessageTransport(WidgetApiDirection.FromWidget, widgetId, window.parent, window);
150+
this.transport = new PostmessageTransport(
151+
WidgetApiDirection.FromWidget,
152+
widgetId,
153+
globalThis.parent,
154+
globalThis,
155+
);
154156
this.transport.targetOrigin = clientOrigin;
155157
this.transport.on("message", this.handleMessage.bind(this));
156158
}
@@ -191,7 +193,9 @@ export class WidgetApi extends EventEmitter {
191193
* @throws Throws if the capabilities negotiation has already started.
192194
*/
193195
public requestCapabilities(capabilities: Capability[]): void {
194-
capabilities.forEach((cap) => this.requestCapability(cap));
196+
for (const cap of capabilities) {
197+
this.requestCapability(cap);
198+
}
195199
}
196200

197201
/**
@@ -474,7 +478,7 @@ export class WidgetApi extends EventEmitter {
474478
}
475479

476480
/**
477-
* @deprecated This currently relies on an unstable MSC (MSC4157).
481+
* @experimental This currently relies on an unstable MSC (MSC4157).
478482
*/
479483
public cancelScheduledDelayedEvent(delayId: string): Promise<IUpdateDelayedEventFromWidgetResponseData> {
480484
return this.transport.send<IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData>(
@@ -487,7 +491,7 @@ export class WidgetApi extends EventEmitter {
487491
}
488492

489493
/**
490-
* @deprecated This currently relies on an unstable MSC (MSC4157).
494+
* @experimental This currently relies on an unstable MSC (MSC4157).
491495
*/
492496
public restartScheduledDelayedEvent(delayId: string): Promise<IUpdateDelayedEventFromWidgetResponseData> {
493497
return this.transport.send<IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData>(
@@ -500,7 +504,7 @@ export class WidgetApi extends EventEmitter {
500504
}
501505

502506
/**
503-
* @deprecated This currently relies on an unstable MSC (MSC4157).
507+
* @experimental This currently relies on an unstable MSC (MSC4157).
504508
*/
505509
public sendScheduledDelayedEvent(delayId: string): Promise<IUpdateDelayedEventFromWidgetResponseData> {
506510
return this.transport.send<IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData>(
@@ -681,7 +685,7 @@ export class WidgetApi extends EventEmitter {
681685
* @param {string} uri The URI to navigate to.
682686
* @returns {Promise<void>} Resolves when complete.
683687
* @throws Throws if the URI is invalid or cannot be processed.
684-
* @deprecated This currently relies on an unstable MSC (MSC2931).
688+
* @experimental This currently relies on an unstable MSC (MSC2931).
685689
*/
686690
public navigateTo(uri: string): Promise<void> {
687691
if (!uri || !uri.startsWith("https://matrix.to/#")) {
@@ -704,7 +708,7 @@ export class WidgetApi extends EventEmitter {
704708
const onUpdateTurnServers = async (ev: CustomEvent<IUpdateTurnServersRequest>): Promise<void> => {
705709
ev.preventDefault();
706710
setTurnServer(ev.detail.data);
707-
await this.transport.reply<IWidgetApiAcknowledgeResponseData>(ev.detail, {});
711+
this.transport.reply<IWidgetApiAcknowledgeResponseData>(ev.detail, {});
708712
};
709713

710714
// Start listening for updates before we even start watching, to catch

src/interfaces/Capabilities.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,31 @@ export enum MatrixCapabilities {
2626
*/
2727
RequiresClient = "io.element.requires_client",
2828
/**
29-
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
29+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
3030
*/
3131
MSC2931Navigate = "org.matrix.msc2931.navigate",
32+
/**
33+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
34+
*/
3235
MSC3846TurnServers = "town.robin.msc3846.turn_servers",
3336
/**
34-
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
37+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
3538
*/
3639
MSC3973UserDirectorySearch = "org.matrix.msc3973.user_directory_search",
3740
/**
38-
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
41+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
3942
*/
4043
MSC4039UploadFile = "org.matrix.msc4039.upload_file",
4144
/**
42-
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
45+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
4346
*/
4447
MSC4039DownloadFile = "org.matrix.msc4039.download_file",
4548
/**
46-
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
49+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
4750
*/
4851
MSC4157SendDelayedEvent = "org.matrix.msc4157.send.delayed_event",
4952
/**
50-
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
53+
* @experimental It is not recommended to rely on this existing - it can be removed without notice.
5154
*/
5255
MSC4157UpdateDelayedEvent = "org.matrix.msc4157.update_delayed_event",
5356
}

0 commit comments

Comments
 (0)