Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ lib/
dist/
.npmrc
.idea
.vscode

# Logs
logs
Expand Down
49 changes: 36 additions & 13 deletions src/ClientWidgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
ISupportedVersionsActionRequest,
ISupportedVersionsActionResponseData,
} from "./interfaces/SupportedVersionsAction";
import { CurrentApiVersions } from "./interfaces/ApiVersion";
import { ApiVersion, CurrentApiVersions, UnstableApiVersion } from "./interfaces/ApiVersion";
import { IScreenshotActionResponseData } from "./interfaces/ScreenshotAction";
import { IVisibilityActionRequestData } from "./interfaces/VisibilityAction";
import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponseData } from "./interfaces/IWidgetApiResponse";
Expand Down Expand Up @@ -138,6 +138,7 @@ import { IUpdateStateToWidgetRequestData } from "./interfaces/UpdateStateAction"
export class ClientWidgetApi extends EventEmitter {
public readonly transport: ITransport;

private cachedWidgetVersions: ApiVersion[] | null = null;
// contentLoadedActionSent is used to check that only one ContentLoaded request is send.
private contentLoadedActionSent = false;
private allowedCapabilities = new Set<Capability>();
Expand Down Expand Up @@ -230,6 +231,24 @@ export class ClientWidgetApi extends EventEmitter {
this.transport.stop();
}

public async getWidgetVersions(): Promise<ApiVersion[]> {
if (Array.isArray(this.cachedWidgetVersions)) {
return Promise.resolve(this.cachedWidgetVersions);
}

try {
const r = await this.transport.send<IWidgetApiRequestEmptyData, ISupportedVersionsActionResponseData>(
WidgetApiToWidgetAction.SupportedApiVersions,
{},
);
this.cachedWidgetVersions = r.supported_versions;
return r.supported_versions;
} catch (e) {
console.warn("non-fatal error getting supported widget versions: ", e);
return [];
}
}

private beginCapabilities(): void {
// widget has loaded - tell all the listeners that
this.emit("preparing");
Expand Down Expand Up @@ -285,7 +304,7 @@ export class ClientWidgetApi extends EventEmitter {

private onIframeLoad(ev: Event): void {
if (this.widget.waitForIframeLoad) {
// If the widget is set to waitForIframeLoad the capabilities immediatly get setup after load.
// If the widget is set to waitForIframeLoad the capabilities immediately get setup after load.
// The client does not wait for the ContentLoaded action.
this.beginCapabilities();
} else {
Expand Down Expand Up @@ -1007,7 +1026,7 @@ export class ClientWidgetApi extends EventEmitter {
public async feedEvent(rawEvent: IRoomEvent, currentViewedRoomId: string): Promise<void>;
/**
* Feeds an event to the widget. As a client you are expected to call this
* for every new event in every room to which you are joined or invited.
* for every new event (including state events) in every room to which you are joined or invited.
* @param {IRoomEvent} rawEvent The event to (try to) send to the widget.
* @returns {Promise<void>} Resolves when delivered or if the widget is not
* able to read the event due to permissions, rejects if the widget failed
Expand Down Expand Up @@ -1087,10 +1106,12 @@ export class ClientWidgetApi extends EventEmitter {
events.push(...stateKeyMap.values());
}
}
await this.transport.send<IUpdateStateToWidgetRequestData>(
WidgetApiToWidgetAction.UpdateState,
{ state: events },
);
if ((await this.getWidgetVersions()).includes(UnstableApiVersion.MSC2762_UPDATE_STATE)) {
// Only send state updates when using UpdateState. Otherwise the SendEvent action will be responsible for state updates.
await this.transport.send<IUpdateStateToWidgetRequestData>(WidgetApiToWidgetAction.UpdateState, {
state: events,
});
}
} finally {
this.flushRoomStateTask = null;
}
Expand Down Expand Up @@ -1149,21 +1170,23 @@ export class ClientWidgetApi extends EventEmitter {
* room state entry.
* @returns {Promise<void>} Resolves when delivered or if the widget is not
* able to receive the room state due to permissions, rejects if the
widget failed to handle the update.
* widget failed to handle the update.
*/
public async feedStateUpdate(rawEvent: IRoomEvent): Promise<void> {
if (rawEvent.state_key === undefined) throw new Error('Not a state event');
if (rawEvent.state_key === undefined) throw new Error("Not a state event");
if (
(rawEvent.room_id === this.viewedRoomId || this.canUseRoomTimeline(rawEvent.room_id))
&& this.canReceiveStateEvent(rawEvent.type, rawEvent.state_key)
) {
// Updates could race with the initial push of the room's state
if (this.pushRoomStateTasks.size === 0) {
// No initial push tasks are pending; safe to send immediately
await this.transport.send<IUpdateStateToWidgetRequestData>(
WidgetApiToWidgetAction.UpdateState,
{ state: [rawEvent] },
);
if ((await this.getWidgetVersions()).includes(UnstableApiVersion.MSC2762_UPDATE_STATE)) {
// Only send state updates when using UpdateState. Otherwise the SendEvent action will be responsible for state updates.
await this.transport.send<IUpdateStateToWidgetRequestData>(WidgetApiToWidgetAction.UpdateState, {
state: [rawEvent],
});
}
} else {
// Lump the update in with whatever data will be sent in the
// initial push later. Even if we set it to an "outdated" entry
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/ApiVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum MatrixApiVersion {

export enum UnstableApiVersion {
MSC2762 = "org.matrix.msc2762",
MSC2762_UPDATE_STATE = "org.matrix.msc2762_update_state",
MSC2871 = "org.matrix.msc2871",
MSC2873 = "org.matrix.msc2873",
MSC2931 = "org.matrix.msc2931",
Expand All @@ -41,6 +42,7 @@ export const CurrentApiVersions: ApiVersion[] = [
MatrixApiVersion.Prerelease2,
//MatrixApiVersion.V010,
UnstableApiVersion.MSC2762,
UnstableApiVersion.MSC2762_UPDATE_STATE,
UnstableApiVersion.MSC2871,
UnstableApiVersion.MSC2873,
UnstableApiVersion.MSC2931,
Expand Down
61 changes: 50 additions & 11 deletions test/ClientWidgetApi-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ import { waitFor } from '@testing-library/dom';

import { ClientWidgetApi } from "../src/ClientWidgetApi";
import { WidgetDriver } from "../src/driver/WidgetDriver";
import { UnstableApiVersion } from '../src/interfaces/ApiVersion';
import { Capability } from '../src/interfaces/Capabilities';
import { IRoomEvent } from '../src/interfaces/IRoomEvent';
import { IWidgetApiRequest } from '../src/interfaces/IWidgetApiRequest';
import { IReadRelationsFromWidgetActionRequest } from '../src/interfaces/ReadRelationsAction';
import { ISupportedVersionsActionRequest } from '../src/interfaces/SupportedVersionsAction';
import { IUserDirectorySearchFromWidgetActionRequest } from '../src/interfaces/UserDirectorySearchAction';
import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from '../src/interfaces/WidgetApiAction';
import { WidgetApiDirection } from '../src/interfaces/WidgetApiDirection';
import { Widget } from '../src/models/Widget';
import { PostmessageTransport } from '../src/transport/PostmessageTransport';
import { CurrentApiVersions, UnstableApiVersion } from "../src/interfaces/ApiVersion";
import { Capability } from "../src/interfaces/Capabilities";
import { IRoomEvent } from "../src/interfaces/IRoomEvent";
import { IWidgetApiRequest } from "../src/interfaces/IWidgetApiRequest";
import { IReadRelationsFromWidgetActionRequest } from "../src/interfaces/ReadRelationsAction";
import { ISupportedVersionsActionRequest } from "../src/interfaces/SupportedVersionsAction";
import { IUserDirectorySearchFromWidgetActionRequest } from "../src/interfaces/UserDirectorySearchAction";
import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "../src/interfaces/WidgetApiAction";
import { WidgetApiDirection } from "../src/interfaces/WidgetApiDirection";
import { Widget } from "../src/models/Widget";
import { PostmessageTransport } from "../src/transport/PostmessageTransport";
import {
IDownloadFileActionFromWidgetActionRequest,
IGetOpenIDActionRequest,
Expand Down Expand Up @@ -792,6 +792,14 @@ describe('ClientWidgetApi', () => {
const roomId = '!room:example.org';
const otherRoomId = '!other-room:example.org';
clientWidgetApi.setViewedRoomId(roomId);

jest.spyOn(transport, "send").mockImplementation((action, data) => {
if (action === WidgetApiToWidgetAction.SupportedApiVersions) {
return Promise.resolve({ supported_versions: CurrentApiVersions });
}
return Promise.resolve({});
});

const topicEvent = createRoomEvent({
room_id: roomId,
type: 'm.room.topic',
Expand Down Expand Up @@ -904,6 +912,37 @@ describe('ClientWidgetApi', () => {
});
});

describe('dont receive UpdateState if version not supported', () => {
it('syncs initial state and feeds updates', async () => {
const roomId = '!room:example.org';
clientWidgetApi.setViewedRoomId(roomId);
jest.spyOn(transport, "send").mockImplementation((action, data) => {
if (action === WidgetApiToWidgetAction.SupportedApiVersions) {
return Promise.resolve({ supported_versions: [] });
}
return Promise.resolve({});
});

await loadIframe([
'org.matrix.msc2762.receive.state_event:m.room.join_rules#',
]);

const newJoinRulesEvent = createRoomEvent({
room_id: roomId,
type: 'm.room.join_rules',
state_key: '',
content: { join_rule: 'invite' },
});
clientWidgetApi.feedStateUpdate(newJoinRulesEvent);

await waitFor(() => {

// Only the updated join rules should have been delivered
expect(transport.send).not.toHaveBeenCalledWith(WidgetApiToWidgetAction.UpdateState);
});
});
});

describe('update_delayed_event action', () => {
it('fails to update delayed events', async () => {
const event: IUpdateDelayedEventFromWidgetActionRequest = {
Expand Down
Loading