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,61 +0,0 @@
import { ClientRendezvousFailureReason, MSC4108FailureReason, type RendezvousFailureListener } from "../index.ts";
import { type MatrixClient } from "../../matrix.ts";
/**
* Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* insecure rendezvous session protocol.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export declare class MSC4108RendezvousSession {
url?: string;
private readonly client?;
private readonly fallbackRzServer?;
private readonly fetchFn?;
onFailure?: RendezvousFailureListener;
private etag?;
private expiresAt?;
private expiresTimer?;
private _cancelled;
private _ready;
constructor({ onFailure, url, fetchFn, }: {
fetchFn?: typeof globalThis.fetch;
onFailure?: RendezvousFailureListener;
url: string;
});
constructor({ onFailure, client, fallbackRzServer, fetchFn, }: {
fetchFn?: typeof globalThis.fetch;
onFailure?: RendezvousFailureListener;
client?: MatrixClient;
fallbackRzServer?: string;
});
/**
* Returns whether the channel is ready to be used.
*/
get ready(): boolean;
/**
* Returns whether the channel has been cancelled.
*/
get cancelled(): boolean;
private fetch;
private getPostEndpoint;
/**
* Sends data via the rendezvous channel.
* @param data the payload to send
*/
send(data: string): Promise<void>;
/**
* Receives data from the rendezvous channel.
* @returns the returned promise won't resolve until new data is acquired or the channel is closed either by the server or the other party.
*/
receive(): Promise<string | undefined>;
/**
* Cancels the rendezvous channel.
* If the reason is user_declined or user_cancelled then the channel will also be closed.
* @param reason the reason to cancel with
*/
cancel(reason: MSC4108FailureReason | ClientRendezvousFailureReason): Promise<void>;
/**
* Closes the rendezvous channel.
*/
close(): Promise<void>;
}
//# sourceMappingURL=MSC4108RendezvousSession.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"MSC4108RendezvousSession.d.ts","sourceRoot":"","sources":["../../../src/rendezvous/transports/MSC4108RendezvousSession.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,6BAA6B,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,KAAK,YAAY,EAAU,MAAM,iBAAiB,CAAC;AAG5D;;;;GAIG;AACH,qBAAa,wBAAwB;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA0B;IAC5C,SAAS,CAAC,EAAE,yBAAyB,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,CAAS;IACtB,OAAO,CAAC,SAAS,CAAC,CAAO;IACzB,OAAO,CAAC,YAAY,CAAC,CAAgC;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAmB,EACf,SAAS,EACT,GAAG,EACH,OAAO,GACV,EAAE;QACC,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;QAClC,SAAS,CAAC,EAAE,yBAAyB,CAAC;QACtC,GAAG,EAAE,MAAM,CAAC;KACf,EAAE;IACH,YAAmB,EACf,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,OAAO,GACV,EAAE;QACC,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;QAClC,SAAS,CAAC,EAAE,yBAAyB,CAAC;QACtC,MAAM,CAAC,EAAE,YAAY,CAAC;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC7B,EAAE;IAqBH;;OAEG;IACH,IAAW,KAAK,IAAI,OAAO,CAE1B;IAED;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED,OAAO,CAAC,KAAK;YAOC,eAAe;IAgB7B;;;OAGG;IACU,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD7C;IAED;;;OAGG;IACU,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0ClD;IAED;;;;OAIG;IACU,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB/F;IAED;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAclC;CACJ"}

View File

@@ -1,234 +0,0 @@
import _defineProperty from "@babel/runtime/helpers/defineProperty";
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger.js";
import { sleep } from "../../utils.js";
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "../index.js";
import { Method } from "../../matrix.js";
import { ClientPrefix } from "../../http-api/index.js";
/**
* Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* insecure rendezvous session protocol.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export class MSC4108RendezvousSession {
constructor({
fetchFn,
onFailure,
url,
client,
fallbackRzServer
}) {
_defineProperty(this, "url", void 0);
_defineProperty(this, "client", void 0);
_defineProperty(this, "fallbackRzServer", void 0);
_defineProperty(this, "fetchFn", void 0);
_defineProperty(this, "onFailure", void 0);
_defineProperty(this, "etag", void 0);
_defineProperty(this, "expiresAt", void 0);
_defineProperty(this, "expiresTimer", void 0);
_defineProperty(this, "_cancelled", false);
_defineProperty(this, "_ready", false);
this.fetchFn = fetchFn;
this.onFailure = onFailure;
this.client = client;
this.fallbackRzServer = fallbackRzServer;
this.url = url;
}
/**
* Returns whether the channel is ready to be used.
*/
get ready() {
return this._ready;
}
/**
* Returns whether the channel has been cancelled.
*/
get cancelled() {
return this._cancelled;
}
fetch(resource, options) {
if (this.fetchFn) {
return this.fetchFn(resource, options);
}
return globalThis.fetch(resource, options);
}
async getPostEndpoint() {
if (this.client) {
try {
if (await this.client.doesServerSupportUnstableFeature("org.matrix.msc4108")) {
return this.client.http.getUrl("/org.matrix.msc4108/rendezvous", undefined, ClientPrefix.Unstable).toString();
}
} catch (err) {
logger.warn("Failed to get unstable features", err);
}
}
return this.fallbackRzServer;
}
/**
* Sends data via the rendezvous channel.
* @param data the payload to send
*/
async send(data) {
if (this._cancelled) {
return;
}
const method = this.url ? Method.Put : Method.Post;
const uri = this.url ?? (await this.getPostEndpoint());
if (!uri) {
throw new Error("Invalid rendezvous URI");
}
const headers = {
"content-type": "text/plain"
};
// if we didn't create the rendezvous channel, we need to fetch the first etag if needed
if (!this.etag && this.url) {
await this.receive();
}
if (this.etag) {
headers["if-match"] = this.etag;
}
logger.info(`=> ${method} ${uri} with ${data} if-match: ${this.etag}`);
const res = await this.fetch(uri, {
method,
headers,
body: data,
redirect: "follow"
});
if (res.status === 404) {
return this.cancel(ClientRendezvousFailureReason.Unknown);
}
this.etag = res.headers.get("etag") ?? undefined;
logger.info(`Received etag: ${this.etag}`);
if (method === Method.Post) {
const expires = res.headers.get("expires");
if (expires) {
if (this.expiresTimer) {
clearTimeout(this.expiresTimer);
this.expiresTimer = undefined;
}
this.expiresAt = new Date(expires);
this.expiresTimer = setTimeout(() => {
this.expiresTimer = undefined;
void this.cancel(ClientRendezvousFailureReason.Expired);
}, this.expiresAt.getTime() - Date.now());
}
// MSC4108: we expect a JSON response with a rendezvous URL
const json = await res.json();
if (typeof json.url !== "string") {
throw new Error("No rendezvous URL given");
}
this.url = json.url;
this._ready = true;
}
}
/**
* Receives data from the rendezvous channel.
* @returns the returned promise won't resolve until new data is acquired or the channel is closed either by the server or the other party.
*/
async receive() {
if (!this.url) {
throw new Error("Rendezvous not set up");
}
while (true) {
if (this._cancelled) {
return undefined;
}
const headers = {};
if (this.etag) {
headers["if-none-match"] = this.etag;
}
logger.info(`=> GET ${this.url} if-none-match: ${this.etag}`);
const poll = await this.fetch(this.url, {
method: Method.Get,
headers
});
if (poll.status === 404) {
await this.cancel(ClientRendezvousFailureReason.Unknown);
return undefined;
}
// rely on server expiring the channel rather than checking ourselves
const etag = poll.headers.get("etag") ?? undefined;
if (poll.headers.get("content-type") !== "text/plain") {
this.etag = etag;
} else if (poll.status === 200) {
if (!etag) {
// Some browsers & extensions block the ETag header for anti-tracking purposes
// We try and detect this so the client can give the user a somewhat helpful message
await this.cancel(ClientRendezvousFailureReason.ETagMissing);
return undefined;
}
this.etag = etag;
const text = await poll.text();
logger.info(`Received: ${text} with etag ${this.etag}`);
return text;
}
await sleep(1000);
}
}
/**
* Cancels the rendezvous channel.
* If the reason is user_declined or user_cancelled then the channel will also be closed.
* @param reason the reason to cancel with
*/
async cancel(reason) {
if (this._cancelled) return;
if (this.expiresTimer) {
clearTimeout(this.expiresTimer);
this.expiresTimer = undefined;
}
if (reason === ClientRendezvousFailureReason.Unknown && this.expiresAt && this.expiresAt.getTime() < Date.now()) {
reason = ClientRendezvousFailureReason.Expired;
}
this._cancelled = true;
this._ready = false;
this.onFailure?.(reason);
if (reason === ClientRendezvousFailureReason.UserDeclined || reason === MSC4108FailureReason.UserCancelled) {
await this.close();
}
}
/**
* Closes the rendezvous channel.
*/
async close() {
if (this.expiresTimer) {
clearTimeout(this.expiresTimer);
this.expiresTimer = undefined;
}
if (!this.url) return;
try {
const method = Method.Delete;
logger.info(`=> ${method} ${this.url}`);
await this.fetch(this.url, {
method
});
} catch (e) {
logger.warn(e);
}
}
}
//# sourceMappingURL=MSC4108RendezvousSession.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
export * from "./MSC4108RendezvousSession.ts";
export * from "./MSC3886SimpleHttpRendezvousTransport";
//# sourceMappingURL=index.d.ts.map

View File

@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rendezvous/transports/index.ts"],"names":[],"mappings":"AAgBA,cAAc,+BAA+B,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rendezvous/transports/index.ts"],"names":[],"mappings":"AAgBA,cAAc,wCAAwC,CAAC"}

View File

@@ -1,18 +1,17 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
"use strict";
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export * from "./MSC4108RendezvousSession.js";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _MSC3886SimpleHttpRendezvousTransport = require("./MSC3886SimpleHttpRendezvousTransport");
Object.keys(_MSC3886SimpleHttpRendezvousTransport).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _MSC3886SimpleHttpRendezvousTransport[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _MSC3886SimpleHttpRendezvousTransport[key];
}
});
});
//# sourceMappingURL=index.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"index.js","names":[],"sources":["../../../src/rendezvous/transports/index.ts"],"sourcesContent":["/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport * from \"./MSC4108RendezvousSession.ts\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,cAAc,+BAA+B","ignoreList":[]}
{"version":3,"file":"index.js","names":["_MSC3886SimpleHttpRendezvousTransport","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get"],"sources":["../../../src/rendezvous/transports/index.ts"],"sourcesContent":["/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport * from \"./MSC3886SimpleHttpRendezvousTransport\";\n"],"mappings":";;;;;AAgBA,IAAAA,qCAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,qCAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,qCAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,qCAAA,CAAAK,GAAA;IAAA;EAAA;AAAA"}