feat: complete Ancestor quote bot with production-ready Docker support

This commit is contained in:
unfunny
2026-09-13 14:14:38 -04:00
parent f2016da05a
commit fe7351a7dc
1145 changed files with 30890 additions and 97684 deletions

View File

@@ -1,5 +1,20 @@
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RoomWidgetClient = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _matrixWidgetApi = require("matrix-widget-api");
var _event = require("./models/event");
var _event2 = require("./@types/event");
var _logger = require("./logger");
var _client = require("./client");
var _sync = require("./sync");
var _slidingSyncSdk = require("./sliding-sync-sdk");
var _user = require("./models/user");
var _utils = require("./utils");
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
@@ -16,516 +31,173 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { WidgetApiToWidgetAction, WidgetApiResponseError, MatrixCapabilities, UnstableApiVersion } from "matrix-widget-api";
import { MatrixEvent, EventStatus } from "./models/event.js";
import { UpdateDelayedEventAction, isSendDelayedEventRequestOpts } from "./@types/requests.js";
import { EventType } from "./@types/event.js";
import { logger } from "./logger.js";
import { MatrixClient, ClientEvent, UNSTABLE_MSC4140_DELAYED_EVENTS } from "./client.js";
import { SyncApi, SyncState } from "./sync.js";
import { SlidingSyncSdk } from "./sliding-sync-sdk.js";
import { ConnectionError, MatrixError } from "./http-api/errors.js";
import { User } from "./models/user.js";
import { MapWithDefault, recursiveMapToObject } from "./utils.js";
import { TypedEventEmitter, UnsupportedDelayedEventsEndpointError } from "./matrix.js";
export let RoomWidgetClientEvent = /*#__PURE__*/function (RoomWidgetClientEvent) {
RoomWidgetClientEvent["PendingEventsChanged"] = "PendingEvent.pendingEventsChanged";
return RoomWidgetClientEvent;
}({});
/**
* A MatrixClient that routes its requests through the widget API instead of the
* real CS API.
* @experimental This class is considered unstable!
*/
export class RoomWidgetClient extends MatrixClient {
/**
*
* @param widgetApi - The widget api to use for communication.
* @param capabilities - The capabilities the widget client will request.
* @param roomId - The room id the widget is associated with.
* @param opts - The configuration options for this client.
* @param sendContentLoaded - Whether to send a content loaded widget action immediately after initial setup.
* Set to `false` if the widget uses `waitForIFrameLoad=true` (in this case the client does not expect a content loaded action at all),
* or if the the widget wants to send the `ContentLoaded` action at a later point in time after the initial setup.
*/
constructor(widgetApi, capabilities, _roomId, opts, sendContentLoaded) {
class RoomWidgetClient extends _client.MatrixClient {
constructor(widgetApi, capabilities, roomId, opts) {
var _capabilities$sendEve, _capabilities$receive, _capabilities$sendSta, _capabilities$receive2, _capabilities$sendEve2, _capabilities$receive3, _capabilities$sendSta2, _capabilities$receive4, _capabilities$sendToD, _capabilities$receive5;
super(opts);
_defineProperty(this, "room", void 0);
_defineProperty(this, "widgetApiReady", void 0);
_defineProperty(this, "roomStateSynced", void 0);
_defineProperty(this, "lifecycle", void 0);
_defineProperty(this, "syncState", null);
_defineProperty(this, "pendingSendingEventsTxId", []);
_defineProperty(this, "eventEmitter", new TypedEventEmitter());
_defineProperty(this, "syncApiResolver", Promise.withResolvers());
_defineProperty(this, "updateTxId", async event => {
// We update the txId for remote echos that originate from this client.
// This happens with the help of `pendingSendingEventsTxId` where we store all events that are currently sending
// with their widget txId and once ready the final evId.
if (
// This could theoretically be an event send by this device
// In that case we need to update the txId of the event because the embedded client/widget
// knows this event with a different transaction Id than what was used by the host client.
event.getSender() === this.getUserId() &&
// We optimize by not blocking events from types that we have not send
// with this client.
this.pendingSendingEventsTxId.some(p => event.getType() === p.type)) {
// Compare by event Id if we have a matching pending event where we know the txId.
let matchingTxId = this.pendingSendingEventsTxId.find(p => p.id === event.getId())?.txId;
// Block any further processing of this event until we have received the sending response.
// -> until we know the event id.
// -> until we have not pending events anymore.
while (!matchingTxId && this.pendingSendingEventsTxId.length > 0) {
// Recheck whenever the PendingEventsChanged
await new Promise(resolve => this.eventEmitter.once(RoomWidgetClientEvent.PendingEventsChanged, () => resolve()));
matchingTxId = this.pendingSendingEventsTxId.find(p => p.id === event.getId())?.txId;
}
// We found the correct txId: we update the event and delete the entry of the pending events.
if (matchingTxId) {
event.setTxnId(matchingTxId);
event.setUnsigned(_objectSpread(_objectSpread({}, event.getUnsigned()), {}, {
transaction_id: matchingTxId
}));
}
this.pendingSendingEventsTxId = this.pendingSendingEventsTxId.filter(p => p.id !== event.getId());
// Emit once there are no pending events anymore to release all other events that got
// awaited in the `while (!matchingTxId && this.pendingSendingEventsTxId.length > 0)` loop
// but are not send by this client.
if (this.pendingSendingEventsTxId.length === 0) {
this.eventEmitter.emit(RoomWidgetClientEvent.PendingEventsChanged);
}
}
});
_defineProperty(this, "onEvent", async ev => {
this.widgetApi = widgetApi;
this.capabilities = capabilities;
this.roomId = roomId;
(0, _defineProperty2.default)(this, "room", void 0);
(0, _defineProperty2.default)(this, "widgetApiReady", void 0);
(0, _defineProperty2.default)(this, "lifecycle", void 0);
(0, _defineProperty2.default)(this, "syncState", null);
(0, _defineProperty2.default)(this, "onEvent", async ev => {
ev.preventDefault();
// Verify the room ID matches, since it's possible for the client to
// send us events from other rooms if this widget is always on screen
if (ev.detail.data.room_id === this.roomId) {
const event = new MatrixEvent(ev.detail.data);
// Only inject once we have update the txId
await this.updateTxId(event);
await this.syncApiResolver.promise;
if (this.syncApi instanceof SyncApi) {
if (await this.supportUpdateState()) {
await this.syncApi.injectRoomEvents(this.room, undefined, [], [event]);
} else {
// Passing undefined for `stateAfterEventList` will make `injectRoomEvents` run in legacy mode
// -> state events in `timelineEventList` will update the state.
await this.syncApi.injectRoomEvents(this.room, [], undefined, [event]);
}
} else {
// Sliding Sync
if (await this.supportUpdateState()) {
await this.syncApi.injectRoomEvents(this.room, [], [event]);
} else {
logger.error("slididng sync cannot be used in widget mode if the client widget driver does not support the version: 'org.matrix.msc2762_update_state'");
}
}
this.emit(ClientEvent.Event, event);
if (event.unstableStickyInfo !== undefined) this.room._unstable_addStickyEvents([event]);
this.setSyncState(SyncState.Syncing);
logger.info(`Received event ${event.getId()} ${event.getType()}`);
const event = new _event.MatrixEvent(ev.detail.data);
await this.syncApi.injectRoomEvents(this.room, [], [event]);
this.emit(_client.ClientEvent.Event, event);
this.setSyncState(_sync.SyncState.Syncing);
_logger.logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
} else {
const {
event_id: eventId,
room_id: roomId
} = ev.detail.data;
logger.info(`Received event ${eventId} for a different room ${roomId}; discarding`);
_logger.logger.info(`Received event ${eventId} for a different room ${roomId}; discarding`);
}
await this.ack(ev);
});
_defineProperty(this, "onToDevice", async ev => {
(0, _defineProperty2.default)(this, "onToDevice", async ev => {
ev.preventDefault();
const event = new MatrixEvent({
const event = new _event.MatrixEvent({
type: ev.detail.data.type,
sender: ev.detail.data.sender,
content: ev.detail.data.content
});
// Mark the event as encrypted if it was, using fake contents and keys since those are unknown to us
if (ev.detail.data.encrypted) event.makeEncrypted(EventType.RoomMessageEncrypted, {}, "", "");
this.emit(ClientEvent.ToDeviceEvent, event);
this.setSyncState(SyncState.Syncing);
if (ev.detail.data.encrypted) event.makeEncrypted(_event2.EventType.RoomMessageEncrypted, {}, "", "");
this.emit(_client.ClientEvent.ToDeviceEvent, event);
this.setSyncState(_sync.SyncState.Syncing);
await this.ack(ev);
});
_defineProperty(this, "onStateUpdate", async ev => {
ev.preventDefault();
if (!(await this.supportUpdateState())) {
logger.warn("received update_state widget action but the widget driver did not claim to support 'org.matrix.msc2762_update_state'");
}
await this.syncApiResolver.promise;
for (const rawEvent of ev.detail.data.state) {
// Verify the room ID matches, since it's possible for the client to
// send us state updates from other rooms if this widget is always
// on screen
if (rawEvent.room_id === this.roomId) {
const event = new MatrixEvent(rawEvent);
if (this.syncApi instanceof SyncApi) {
await this.syncApi.injectRoomEvents(this.room, undefined, [event]);
} else {
// Sliding Sync
await this.syncApi.injectRoomEvents(this.room, [event]);
}
logger.debug(`Updated state entry ${event.getType()} ${event.getStateKey()} to ${event.getId()}`);
} else {
const {
event_id: eventId,
room_id: roomId
} = ev.detail.data;
logger.info(`Received state entry ${eventId} for a different room ${roomId}; discarding`);
}
}
await this.ack(ev);
});
this.widgetApi = widgetApi;
this.capabilities = capabilities;
this.roomId = _roomId;
const transportSend = this.widgetApi.transport.send.bind(this.widgetApi.transport);
this.widgetApi.transport.send = async (action, data) => {
try {
return await transportSend(action, data);
} catch (error) {
processAndThrow(error);
}
};
const transportSendComplete = this.widgetApi.transport.sendComplete.bind(this.widgetApi.transport);
this.widgetApi.transport.sendComplete = async (action, data) => {
try {
return await transportSendComplete(action, data);
} catch (error) {
processAndThrow(error);
}
};
this.widgetApiReady = new Promise(resolve => this.widgetApi.once("ready", resolve));
this.roomStateSynced = capabilities.receiveState?.length ? new Promise(resolve => this.widgetApi.once(`action:${WidgetApiToWidgetAction.UpdateState}`, resolve)) : Promise.resolve();
this.requestInitialCapabilities(capabilities, _roomId);
widgetApi.on(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
widgetApi.on(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
widgetApi.on(`action:${WidgetApiToWidgetAction.UpdateState}`, this.onStateUpdate);
// Request capabilities for the functionality this client needs to support
if ((_capabilities$sendEve = capabilities.sendEvent) !== null && _capabilities$sendEve !== void 0 && _capabilities$sendEve.length || (_capabilities$receive = capabilities.receiveEvent) !== null && _capabilities$receive !== void 0 && _capabilities$receive.length || capabilities.sendMessage === true || Array.isArray(capabilities.sendMessage) && capabilities.sendMessage.length || capabilities.receiveMessage === true || Array.isArray(capabilities.receiveMessage) && capabilities.receiveMessage.length || (_capabilities$sendSta = capabilities.sendState) !== null && _capabilities$sendSta !== void 0 && _capabilities$sendSta.length || (_capabilities$receive2 = capabilities.receiveState) !== null && _capabilities$receive2 !== void 0 && _capabilities$receive2.length) {
widgetApi.requestCapabilityForRoomTimeline(roomId);
}
(_capabilities$sendEve2 = capabilities.sendEvent) === null || _capabilities$sendEve2 === void 0 ? void 0 : _capabilities$sendEve2.forEach(eventType => widgetApi.requestCapabilityToSendEvent(eventType));
(_capabilities$receive3 = capabilities.receiveEvent) === null || _capabilities$receive3 === void 0 ? void 0 : _capabilities$receive3.forEach(eventType => widgetApi.requestCapabilityToReceiveEvent(eventType));
if (capabilities.sendMessage === true) {
widgetApi.requestCapabilityToSendMessage();
} else if (Array.isArray(capabilities.sendMessage)) {
capabilities.sendMessage.forEach(msgType => widgetApi.requestCapabilityToSendMessage(msgType));
}
if (capabilities.receiveMessage === true) {
widgetApi.requestCapabilityToReceiveMessage();
} else if (Array.isArray(capabilities.receiveMessage)) {
capabilities.receiveMessage.forEach(msgType => widgetApi.requestCapabilityToReceiveMessage(msgType));
}
(_capabilities$sendSta2 = capabilities.sendState) === null || _capabilities$sendSta2 === void 0 ? void 0 : _capabilities$sendSta2.forEach(({
eventType,
stateKey
}) => widgetApi.requestCapabilityToSendState(eventType, stateKey));
(_capabilities$receive4 = capabilities.receiveState) === null || _capabilities$receive4 === void 0 ? void 0 : _capabilities$receive4.forEach(({
eventType,
stateKey
}) => widgetApi.requestCapabilityToReceiveState(eventType, stateKey));
(_capabilities$sendToD = capabilities.sendToDevice) === null || _capabilities$sendToD === void 0 ? void 0 : _capabilities$sendToD.forEach(eventType => widgetApi.requestCapabilityToSendToDevice(eventType));
(_capabilities$receive5 = capabilities.receiveToDevice) === null || _capabilities$receive5 === void 0 ? void 0 : _capabilities$receive5.forEach(eventType => widgetApi.requestCapabilityToReceiveToDevice(eventType));
if (capabilities.turnServers) {
widgetApi.requestCapability(_matrixWidgetApi.MatrixCapabilities.MSC3846TurnServers);
}
widgetApi.on(`action:${_matrixWidgetApi.WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
widgetApi.on(`action:${_matrixWidgetApi.WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
// Open communication with the host
widgetApi.start();
// Send a content loaded event now we've started the widget API
// Note that element-web currently does not use waitForIFrameLoad=false and so
// does *not* (yes, that is the right way around) wait for this event. Let's
// start sending this, then once this has rolled out, we can change element-web to
// use waitForIFrameLoad=false and have a widget API that's less racy.
if (sendContentLoaded) widgetApi.sendContentLoaded();
}
requestInitialCapabilities(capabilities, roomId) {
// Request capabilities for the functionality this client needs to support
if (capabilities.sendEvent?.length || capabilities.receiveEvent?.length || capabilities.sendMessage === true || Array.isArray(capabilities.sendMessage) && capabilities.sendMessage.length || capabilities.receiveMessage === true || Array.isArray(capabilities.receiveMessage) && capabilities.receiveMessage.length || capabilities.sendState?.length || capabilities.receiveState?.length) {
this.widgetApi.requestCapabilityForRoomTimeline(roomId);
}
capabilities.sendEvent?.forEach(eventType => this.widgetApi.requestCapabilityToSendEvent(eventType));
capabilities.receiveEvent?.forEach(eventType => this.widgetApi.requestCapabilityToReceiveEvent(eventType));
if (capabilities.sendMessage === true) {
this.widgetApi.requestCapabilityToSendMessage();
} else if (Array.isArray(capabilities.sendMessage)) {
capabilities.sendMessage.forEach(msgType => this.widgetApi.requestCapabilityToSendMessage(msgType));
}
if (capabilities.receiveMessage === true) {
this.widgetApi.requestCapabilityToReceiveMessage();
} else if (Array.isArray(capabilities.receiveMessage)) {
capabilities.receiveMessage.forEach(msgType => this.widgetApi.requestCapabilityToReceiveMessage(msgType));
}
capabilities.sendState?.forEach(({
eventType,
stateKey
}) => this.widgetApi.requestCapabilityToSendState(eventType, stateKey));
capabilities.receiveState?.forEach(({
eventType,
stateKey
}) => this.widgetApi.requestCapabilityToReceiveState(eventType, stateKey));
capabilities.sendToDevice?.forEach(eventType => this.widgetApi.requestCapabilityToSendToDevice(eventType));
capabilities.receiveToDevice?.forEach(eventType => this.widgetApi.requestCapabilityToReceiveToDevice(eventType));
if (capabilities.sendDelayedEvents && (capabilities.sendEvent?.length || capabilities.sendMessage === true || Array.isArray(capabilities.sendMessage) && capabilities.sendMessage.length || capabilities.sendState?.length)) {
this.widgetApi.requestCapability(MatrixCapabilities.MSC4157SendDelayedEvent);
}
if (capabilities.updateDelayedEvents) {
this.widgetApi.requestCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent);
}
if (capabilities.sendSticky) {
this.widgetApi.requestCapability(MatrixCapabilities.MSC4407SendStickyEvent);
}
if (capabilities.receiveSticky) {
this.widgetApi.requestCapability(MatrixCapabilities.MSC4407ReceiveStickyEvent);
}
if (capabilities.turnServers) {
this.widgetApi.requestCapability(MatrixCapabilities.MSC3846TurnServers);
}
if (capabilities.rtcTransports) {
this.widgetApi.requestCapability(MatrixCapabilities.MSC4515RtcTransports);
}
}
async supportUpdateState() {
return (await this.widgetApi.getClientVersions()).includes(UnstableApiVersion.MSC2762_UPDATE_STATE);
}
async startClient(opts = {}) {
var _this$capabilities$re, _this$capabilities$re2;
this.lifecycle = new AbortController();
// Create our own user object artificially (instead of waiting for sync)
// so it's always available, even if the user is not in any rooms etc.
const userId = this.getUserId();
if (userId) {
this.store.storeUser(new User(userId));
this.store.storeUser(new _user.User(userId));
}
// Even though we have no access token and cannot sync, the sync class
// still has some valuable helper methods that we make use of, so we
// instantiate it anyways
if (opts.slidingSync) {
this.syncApi = new SlidingSyncSdk(opts.slidingSync, this, opts, this.buildSyncApiOptions());
this.syncApi = new _slidingSyncSdk.SlidingSyncSdk(opts.slidingSync, this, opts, this.buildSyncApiOptions());
} else {
this.syncApi = new SyncApi(this, opts, this.buildSyncApiOptions());
this.syncApi = new _sync.SyncApi(this, opts, this.buildSyncApiOptions());
}
this.syncApiResolver.resolve();
this.room = this.syncApi.createRoom(this.roomId);
this.store.storeRoom(this.room);
await this.widgetApiReady;
// sync room state:
if (await this.supportUpdateState()) {
// This will resolve once the client driver has sent us all the allowed room state.
await this.roomStateSynced;
} else {
// Backfill the requested events
// We only get the most recent event for every type + state key combo,
// so it doesn't really matter what order we inject them in
await Promise.all(this.capabilities.receiveState?.map(async ({
eventType,
stateKey
}) => {
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]);
const events = rawEvents.map(rawEvent => new MatrixEvent(rawEvent));
if (this.syncApi instanceof SyncApi) {
// Passing events as `stateAfterEventList` will update the state.
await this.syncApi.injectRoomEvents(this.room, undefined, events);
} else {
await this.syncApi.injectRoomEvents(this.room, events); // Sliding Sync
}
events.forEach(event => {
this.emit(ClientEvent.Event, event);
logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
});
}) ?? []);
}
this.cachedWellKnown.start(opts.clientWellKnownPollPeriod !== undefined ? 1000 * opts.clientWellKnownPollPeriod : undefined);
this.setSyncState(SyncState.Syncing);
logger.info("Finished initial sync");
this.matrixRTC.start();
// Backfill the requested events
// We only get the most recent event for every type + state key combo,
// so it doesn't really matter what order we inject them in
await Promise.all((_this$capabilities$re = (_this$capabilities$re2 = this.capabilities.receiveState) === null || _this$capabilities$re2 === void 0 ? void 0 : _this$capabilities$re2.map(async ({
eventType,
stateKey
}) => {
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]);
const events = rawEvents.map(rawEvent => new _event.MatrixEvent(rawEvent));
await this.syncApi.injectRoomEvents(this.room, [], events);
events.forEach(event => {
this.emit(_client.ClientEvent.Event, event);
_logger.logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
});
})) !== null && _this$capabilities$re !== void 0 ? _this$capabilities$re : []);
this.setSyncState(_sync.SyncState.Syncing);
_logger.logger.info("Finished backfilling events");
// Watch for TURN servers, if requested
if (this.capabilities.turnServers) this.watchTurnServers();
}
stopClient() {
this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
this.widgetApi.off(`action:${WidgetApiToWidgetAction.UpdateState}`, this.onStateUpdate);
this.widgetApi.off(`action:${_matrixWidgetApi.WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
this.widgetApi.off(`action:${_matrixWidgetApi.WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
super.stopClient();
this.lifecycle.abort(); // Signal to other async tasks that the client has stopped
}
async joinRoom(roomIdOrAlias) {
if (roomIdOrAlias === this.roomId) return this.room;
throw new Error(`Unknown room: ${roomIdOrAlias}`);
}
async encryptAndSendEvent(room, event, delayOptsOrQuery, queryDict) {
let queryOpts = queryDict;
let delayOpts;
if (delayOptsOrQuery && isSendDelayedEventRequestOpts(delayOptsOrQuery)) {
delayOpts = delayOptsOrQuery;
} else if (!queryOpts) {
queryOpts = delayOptsOrQuery;
}
const stickyDurationMs = queryOpts?.["org.matrix.msc4354.sticky_duration_ms"];
if (stickyDurationMs !== undefined && typeof stickyDurationMs !== "number") {
throw new Error("Sticky duration must be a number when defined");
}
// This is save since we just checked that above
// We need the additional as assertion for the EW linter to be happy.
// It is not capable of implying the type based on the throw if `stickyDurationMs !== undefined && typeof stickyDurationMs !== "number"`
// above
const stickyDurationMsAsNumber = stickyDurationMs;
// We need to extend the content with the redacts parameter
// The js sdk uses event.redacts but the widget api uses event.content.redacts
// This will be converted back to event.redacts in the widget driver.
const content = event.event.redacts ? _objectSpread(_objectSpread({}, event.getContent()), {}, {
redacts: event.event.redacts
}) : event.getContent();
// Delayed event special case.
if (delayOpts) {
// TODO: updatePendingEvent for delayed events?
const response = await this.widgetApi.sendRoomEvent(event.getType(), content, room.roomId, "delay" in delayOpts ? delayOpts.delay : undefined, "parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined, stickyDurationMsAsNumber).catch(timeoutToConnectionError);
return this.validateSendDelayedEventResponse(response);
}
const txId = event.getTxnId();
// Add the txnId to the pending list (still with unknown evID)
if (txId) this.pendingSendingEventsTxId.push({
type: event.getType(),
id: undefined,
txId
});
async encryptAndSendEvent(room, event) {
let response;
try {
response = await this.widgetApi.sendRoomEvent(event.getType(), content, room.roomId, undefined, undefined, stickyDurationMsAsNumber).catch(timeoutToConnectionError);
response = await this.widgetApi.sendRoomEvent(event.getType(), event.getContent(), room.roomId);
} catch (e) {
this.updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
this.updatePendingEventStatus(room, event, _event.EventStatus.NOT_SENT);
throw e;
}
// This also checks for an event id on the response
room.updatePendingEvent(event, EventStatus.SENT, response.event_id);
// Update the pending events list with the eventId
this.pendingSendingEventsTxId.forEach(p => {
if (p.txId === txId) p.id = response.event_id;
});
this.eventEmitter.emit(RoomWidgetClientEvent.PendingEventsChanged);
room.updatePendingEvent(event, _event.EventStatus.SENT, response.event_id);
return {
event_id: response.event_id
};
}
async sendStateEvent(roomId, eventType, content, stateKey = "") {
const response = await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId).catch(timeoutToConnectionError);
if (response.event_id === undefined) {
throw new Error("'event_id' absent from response to an event request");
}
return {
event_id: response.event_id
};
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
async _unstable_sendDelayedStateEvent(roomId, delayOpts, eventType, content, stateKey = "") {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw new UnsupportedDelayedEventsEndpointError("Server does not support the delayed events API", "sendDelayedStateEvent");
}
const response = await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId, "delay" in delayOpts ? delayOpts.delay : undefined, "parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined).catch(timeoutToConnectionError);
return this.validateSendDelayedEventResponse(response);
}
validateSendDelayedEventResponse(response) {
if (response.delay_id === undefined) {
throw new Error("'delay_id' absent from response to a delayed event request");
}
return {
delay_id: response.delay_id
};
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
* @deprecated Instead use one of:
* - {@link _unstable_cancelScheduledDelayedEvent}
* - {@link _unstable_restartScheduledDelayedEvent}
* - {@link _unstable_sendScheduledDelayedEvent}
*/
async _unstable_updateDelayedEvent(delayId, action) {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw new UnsupportedDelayedEventsEndpointError("Server does not support the delayed events API", "updateDelayedEvent");
}
switch (action) {
case UpdateDelayedEventAction.Cancel:
await this.widgetApi.cancelScheduledDelayedEvent(delayId).catch(timeoutToConnectionError);
break;
case UpdateDelayedEventAction.Restart:
await this.widgetApi.restartScheduledDelayedEvent(delayId).catch(timeoutToConnectionError);
break;
case UpdateDelayedEventAction.Send:
await this.widgetApi.sendScheduledDelayedEvent(delayId).catch(timeoutToConnectionError);
break;
}
return {};
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
async _unstable_cancelScheduledDelayedEvent(delayId) {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw new UnsupportedDelayedEventsEndpointError("Server does not support the delayed events API", "cancelScheduledDelayedEvent");
}
await this.widgetApi.cancelScheduledDelayedEvent(delayId).catch(timeoutToConnectionError);
return {};
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
async _unstable_restartScheduledDelayedEvent(delayId) {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw new UnsupportedDelayedEventsEndpointError("Server does not support the delayed events API", "restartScheduledDelayedEvent");
}
await this.widgetApi.restartScheduledDelayedEvent(delayId).catch(timeoutToConnectionError);
return {};
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
async _unstable_sendScheduledDelayedEvent(delayId) {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw new UnsupportedDelayedEventsEndpointError("Server does not support the delayed events API", "sendScheduledDelayedEvent");
}
await this.widgetApi.sendScheduledDelayedEvent(delayId).catch(timeoutToConnectionError);
return {};
}
/**
* by {@link MatrixClient.encryptAndSendToDevice}.
*/
async encryptAndSendToDevice(eventType, devices, payload) {
// map: user Id → device Id → payload
const contentMap = new MapWithDefault(() => new Map());
for (const {
userId,
deviceId
} of devices) {
contentMap.getOrCreate(userId).set(deviceId, payload);
}
await this.widgetApi.sendToDevice(eventType, true, recursiveMapToObject(contentMap)).catch(timeoutToConnectionError);
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
}
async sendToDevice(eventType, contentMap) {
await this.widgetApi.sendToDevice(eventType, false, recursiveMapToObject(contentMap)).catch(timeoutToConnectionError);
await this.widgetApi.sendToDevice(eventType, false, (0, _utils.recursiveMapToObject)(contentMap));
return {};
}
async getOpenIdToken() {
const token = await this.widgetApi.requestOpenIDConnectToken().catch(timeoutToConnectionError);
// the IOpenIDCredentials from the widget-api and IOpenIDToken form the matrix-js-sdk are compatible.
// we still recreate the token to make this transparent and catch'able by the linter in case the types change in the future.
return {
access_token: token.access_token,
expires_in: token.expires_in,
matrix_server_name: token.matrix_server_name,
token_type: token.token_type
};
}
/**
* Returns a set of configured RTC transports supported by the homeserver.
*
* Overrides the homeserver-side {@link MatrixClient._unstable_getRTCTransports} (MSC4143):
* a widget cannot make authenticated homeserver calls itself, so we ask the host over the
* widget API instead (MSC4515). Requires the `rtcTransports` capability and a host that
* advertises the `org.matrix.msc4515` API version (otherwise the request throws).
*/
async _unstable_getRTCTransports() {
const {
rtc_transports: rtcTransports
} = await this.widgetApi.getRtcTransports().catch(timeoutToConnectionError);
return rtcTransports;
}
async queueToDevice({
eventType,
batch
}) {
// map: user Id → device Id → payload
const contentMap = new MapWithDefault(() => new Map());
const contentMap = new _utils.MapWithDefault(() => new Map());
for (const {
userId,
deviceId,
@@ -533,24 +205,20 @@ export class RoomWidgetClient extends MatrixClient {
} of batch) {
contentMap.getOrCreate(userId).set(deviceId, payload);
}
await this.widgetApi.sendToDevice(eventType, false, recursiveMapToObject(contentMap)).catch(timeoutToConnectionError);
await this.widgetApi.sendToDevice(eventType, false, (0, _utils.recursiveMapToObject)(contentMap));
}
/**
* Send an event to a specific list of devices via the widget API. Optionally encrypts the event.
*
* If you are using a full MatrixClient you would be calling {@link MatrixClient.getCrypto().encryptToDeviceMessages()} followed
* by {@link MatrixClient.queueToDevice}.
*
* However, this is combined into a single step when running as an embedded widget client. So, we expose this method for those
* that need it.
*
* @param eventType - Type of the event to send.
* @param encrypted - Whether the event should be encrypted.
* @param contentMap - The content to send. Map from user_id to device_id to content object.
*/
async sendToDeviceViaWidgetApi(eventType, encrypted, contentMap) {
await this.widgetApi.sendToDevice(eventType, encrypted, recursiveMapToObject(contentMap)).catch(timeoutToConnectionError);
async encryptAndSendToDevices(userDeviceInfoArr, payload) {
// map: user Id → device Id → payload
const contentMap = new _utils.MapWithDefault(() => new Map());
for (const {
userId,
deviceInfo: {
deviceId
}
} of userDeviceInfoArr) {
contentMap.getOrCreate(userId).set(deviceId, payload);
}
await this.widgetApi.sendToDevice(payload.type, true, (0, _utils.recursiveMapToObject)(contentMap));
}
// Overridden since we get TURN servers automatically over the widget API,
@@ -566,10 +234,10 @@ export class RoomWidgetClient extends MatrixClient {
setSyncState(state) {
const oldState = this.syncState;
this.syncState = state;
this.emit(ClientEvent.Sync, state, oldState);
this.emit(_client.ClientEvent.Sync, state, oldState);
}
async ack(ev) {
this.widgetApi.transport.reply(ev.detail, {});
await this.widgetApi.transport.reply(ev.detail, {});
}
async watchTurnServers() {
const servers = this.widgetApi.getTurnServers();
@@ -584,34 +252,15 @@ export class RoomWidgetClient extends MatrixClient {
username: server.username,
credential: server.password
}];
this.emit(ClientEvent.TurnServers, this.turnServers);
logger.log(`Received TURN server: ${server.uris}`);
this.emit(_client.ClientEvent.TurnServers, this.turnServers);
_logger.logger.log(`Received TURN server: ${server.uris}`);
}
} catch (e) {
logger.warn("Error watching TURN servers", e);
_logger.logger.warn("Error watching TURN servers", e);
} finally {
this.lifecycle.signal.removeEventListener("abort", onClientStopped);
}
}
}
function processAndThrow(error) {
if (error instanceof WidgetApiResponseError && error.data.matrix_api_error) {
throw MatrixError.fromWidgetApiErrorData(error.data.matrix_api_error);
} else {
throw error;
}
}
/**
* This converts an "Request timed out" error from the PostmessageTransport into a ConnectionError.
* It either throws the original error or a new ConnectionError.
**/
function timeoutToConnectionError(error) {
// TODO: this should not check on error.message but instead it should be a specific type
// error instanceof WidgetTimeoutError
if (error instanceof Error && error.message === "Request timed out") {
throw new ConnectionError("widget api timeout");
}
throw error;
}
exports.RoomWidgetClient = RoomWidgetClient;
//# sourceMappingURL=embedded.js.map