This commit is contained in:
unfunny
2026-09-12 23:57:45 -04:00
parent 986c0af682
commit dfe4cc537f
1775 changed files with 246421 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
import { ClientRendezvousFailureReason, MSC4108FailureReason, type RendezvousFailureListener } from "./index.ts";
import { type MatrixClient } from "../client.ts";
import { type MSC4108SecureChannel } from "./channels/MSC4108SecureChannel.ts";
import { type DeviceAccessTokenResponse, type ValidatedAuthMetadata } from "../oauth/index.ts";
import { type CryptoApi } from "../crypto-api/index.ts";
/**
* Enum representing the payload types transmissible over [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* secure channels.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export declare enum PayloadType {
Protocols = "m.login.protocols",
Protocol = "m.login.protocol",
Failure = "m.login.failure",
Success = "m.login.success",
Secrets = "m.login.secrets",
ProtocolAccepted = "m.login.protocol_accepted",
Declined = "m.login.declined"
}
/**
* Type representing the base payload format for [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* messages sent over the secure channel.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export interface MSC4108Payload {
type: PayloadType;
}
interface SecretsPayload extends MSC4108Payload, Awaited<ReturnType<NonNullable<CryptoApi["exportSecretsBundle"]>>> {
type: PayloadType.Secrets;
}
/**
* Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* sign in with QR + OAuth2 flow.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export declare class MSC4108SignInWithQR {
private readonly channel;
private readonly didScanCode;
private readonly client?;
onFailure?: RendezvousFailureListener | undefined;
private readonly ourIntent;
private _code?;
private expectingNewDeviceId?;
private metadata?;
private grantInProgress?;
/**
* Returns the check code for the secure channel or undefined if not generated yet.
*/
get checkCode(): string | undefined;
/**
* @param channel - The secure channel used for communication
* @param client - The Matrix client in used on the device already logged in
* @param didScanCode - Whether this side of the channel scanned the QR code from the other party
* @param onFailure - Callback for when the rendezvous fails
*/
constructor(channel: MSC4108SecureChannel, didScanCode: boolean, client?: MatrixClient | undefined, onFailure?: RendezvousFailureListener | undefined);
/**
* Returns the code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
*/
get code(): Uint8Array | undefined;
/**
* Generate the code including doing partial set up of the channel where required.
*/
generateCode(): Promise<void>;
/**
* Returns true if the device is the already logged in device reciprocating a new login on the other side of the channel.
*/
get isExistingDevice(): boolean;
/**
* Returns true if the device is the new device logging in being reciprocated by the device on the other side of the channel.
*/
get isNewDevice(): boolean;
/**
* The first step in the OAuth2 QR login process.
* To be called after the QR code has been rendered or scanned.
* The scanning device has to discover the homeserver details, if they scanned the code then they already have it.
* If the new device is the one rendering the QR code then it has to wait be sent the homeserver details via the rendezvous channel.
*/
negotiateProtocols(): Promise<{
serverName?: string;
}>;
/**
* The second & third step in the OAuth2 QR login process.
* To be called after `negotiateProtocols` for the existing device.
* To be called after OAuth2 negotiation for the new device.
*
* @param input - Required for the new device to start the device authorization grant, not required for the existing device reciprocating the login
*/
deviceAuthorizationGrant(input?: {
metadata: ValidatedAuthMetadata;
clientId: string;
deviceId: string;
}): Promise<{
verificationUri?: string;
userCode?: string;
}>;
/**
* The fourth step in the OAuth2 QR login process.
* The reciprocating device must perform step 5 for this method to resolve.
* To be called after {@link deviceAuthorizationGrant} only on the new device.
*/
completeLoginOnNewDevice({ clientId, }: {
clientId: string;
}): Promise<DeviceAccessTokenResponse | undefined>;
/**
* The fifth (and final) step in the OAuth2 QR login process.
* To be called after the new device has completed authentication.
*/
shareSecrets(): Promise<{
secrets?: Omit<SecretsPayload, "type">;
}>;
private receive;
private send;
/**
* Decline the login on the existing device.
*/
declineLoginOnExistingDevice(): Promise<void>;
/**
* Cancels the rendezvous session.
* @param reason the reason for the cancellation
*/
cancel(reason: MSC4108FailureReason | ClientRendezvousFailureReason): Promise<void>;
/**
* Closes the rendezvous session.
*/
close(): Promise<void>;
}
export {};
//# sourceMappingURL=MSC4108SignInWithQR.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MSC4108SignInWithQR.d.ts","sourceRoot":"","sources":["../../src/rendezvous/MSC4108SignInWithQR.ts"],"names":[],"mappings":"AAkBA,OAAO,EACH,6BAA6B,EAC7B,oBAAoB,EAEpB,KAAK,yBAAyB,EACjC,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAG/E,OAAO,EACH,KAAK,yBAAyB,EAK9B,KAAK,qBAAqB,EAE7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;GAIG;AACH,oBAAY,WAAW;IACnB,SAAS,sBAAsB;IAC/B,QAAQ,qBAAqB;IAC7B,OAAO,oBAAoB;IAC3B,OAAO,oBAAoB;IAC3B,OAAO,oBAAoB;IAC3B,gBAAgB,8BAA8B;IAC9C,QAAQ,qBAAqB;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,WAAW,CAAC;CACrB;AA8CD,UAAU,cAAe,SAAQ,cAAc,EAAE,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC/G,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAqBxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,yBAAyB;IAvBhD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,KAAK,CAAC,CAAa;IAC3B,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,eAAe,CAAC,CAA8B;IAEtD;;OAEG;IACH,IAAW,SAAS,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED;;;;;OAKG;IACH,YACqB,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,OAAO,EACpB,MAAM,CAAC,EAAE,YAAY,YAAA,EAC/B,SAAS,CAAC,EAAE,yBAAyB,YAAA,EAG/C;IAED;;OAEG;IACH,IAAW,IAAI,IAAI,UAAU,GAAG,SAAS,CAExC;IAED;;OAEG;IACU,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAUzC;IAED;;OAEG;IACH,IAAW,gBAAgB,IAAI,OAAO,CAErC;IAED;;OAEG;IACH,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED;;;;;OAKG;IACU,kBAAkB,IAAI,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CA4DlE;IAED;;;;;;OAMG;IACU,wBAAwB,CAAC,KAAK,CAAC,EAAE;QAC1C,QAAQ,EAAE,qBAAqB,CAAC;QAChC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QACR,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAgGD;IAED;;;;OAIG;IACU,wBAAwB,CAAC,EAClC,QAAQ,GACX,EAAE;QACC,QAAQ,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAoDjD;IAED;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAuF/E;YAEa,OAAO;YAIP,IAAI;IAIlB;;OAEG;IACU,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAQzD;IAED;;;OAGG;IACU,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAG/F;IAED;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAElC;CACJ"}

View File

@@ -0,0 +1,439 @@
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
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 { QrCodeIntent } from "@matrix-org/matrix-sdk-crypto-wasm";
import { ClientRendezvousFailureReason, MSC4108FailureReason, RendezvousError } from "./index.js";
import { logger } from "../logger.js";
import { MatrixError } from "../http-api/index.js";
import { sleep } from "../utils.js";
import { generateScope, OAuthGrantType, startDeviceAuthorization, waitForDeviceAuthorization } from "../oauth/index.js";
/**
* Enum representing the payload types transmissible over [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* secure channels.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export let PayloadType = /*#__PURE__*/function (PayloadType) {
PayloadType["Protocols"] = "m.login.protocols";
PayloadType["Protocol"] = "m.login.protocol";
PayloadType["Failure"] = "m.login.failure";
PayloadType["Success"] = "m.login.success";
PayloadType["Secrets"] = "m.login.secrets";
PayloadType["ProtocolAccepted"] = "m.login.protocol_accepted";
PayloadType["Declined"] = "m.login.declined";
return PayloadType;
}({});
/**
* Type representing the base payload format for [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* messages sent over the secure channel.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
function isDeviceAuthorizationGrantProtocolPayload(payload) {
return payload.protocol === "device_authorization_grant";
}
/**
* Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* sign in with QR + OAuth2 flow.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
*/
export class MSC4108SignInWithQR {
/**
* Returns the check code for the secure channel or undefined if not generated yet.
*/
get checkCode() {
return this.channel?.getCheckCode();
}
/**
* @param channel - The secure channel used for communication
* @param client - The Matrix client in used on the device already logged in
* @param didScanCode - Whether this side of the channel scanned the QR code from the other party
* @param onFailure - Callback for when the rendezvous fails
*/
constructor(channel, didScanCode, client, onFailure) {
_defineProperty(this, "ourIntent", void 0);
_defineProperty(this, "_code", void 0);
_defineProperty(this, "expectingNewDeviceId", void 0);
_defineProperty(this, "metadata", void 0);
_defineProperty(this, "grantInProgress", void 0);
this.channel = channel;
this.didScanCode = didScanCode;
this.client = client;
this.onFailure = onFailure;
this.ourIntent = client ? QrCodeIntent.Reciprocate : QrCodeIntent.Login;
}
/**
* Returns the code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
*/
get code() {
return this._code;
}
/**
* Generate the code including doing partial set up of the channel where required.
*/
async generateCode() {
if (this._code) {
return;
}
if (this.ourIntent === QrCodeIntent.Reciprocate && this.client) {
this._code = await this.channel.generateCode(this.ourIntent, this.client.getDomain());
} else if (this.ourIntent === QrCodeIntent.Login) {
this._code = await this.channel.generateCode(this.ourIntent);
}
}
/**
* Returns true if the device is the already logged in device reciprocating a new login on the other side of the channel.
*/
get isExistingDevice() {
return this.ourIntent === QrCodeIntent.Reciprocate;
}
/**
* Returns true if the device is the new device logging in being reciprocated by the device on the other side of the channel.
*/
get isNewDevice() {
return !this.isExistingDevice;
}
/**
* The first step in the OAuth2 QR login process.
* To be called after the QR code has been rendered or scanned.
* The scanning device has to discover the homeserver details, if they scanned the code then they already have it.
* If the new device is the one rendering the QR code then it has to wait be sent the homeserver details via the rendezvous channel.
*/
async negotiateProtocols() {
logger.info(`negotiateProtocols(isNewDevice=${this.isNewDevice} didScanCode=${this.didScanCode})`);
await this.channel.connect();
if (this.didScanCode) {
// Secure Channel step 6 completed, we trust the channel
if (this.isNewDevice) {
// MSC4108-Flow: ExistingScanned - take homeserver from QR code which should already be set
} else {
// MSC4108-Flow: NewScanned -send protocols message
let authMetadata;
try {
authMetadata = await this.client.getAuthMetadata();
} catch (e) {
logger.error("Failed to discover OAuth2 metadata", e);
}
if (authMetadata?.grant_types_supported.includes(OAuthGrantType.DeviceAuthorization)) {
await this.send({
type: PayloadType.Protocols,
protocols: ["device_authorization_grant"],
homeserver: this.client.getDomain()
});
} else {
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UnsupportedProtocol
});
throw new RendezvousError("Device code grant unsupported", MSC4108FailureReason.UnsupportedProtocol);
}
}
} else if (this.isNewDevice) {
// MSC4108-Flow: ExistingScanned - wait for protocols message
logger.info("Waiting for protocols message");
const payload = await this.receive();
if (payload?.type === PayloadType.Failure) {
throw new RendezvousError("Failed", payload.reason);
}
if (payload?.type !== PayloadType.Protocols) {
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UnexpectedMessageReceived
});
throw new RendezvousError("Unexpected message received", MSC4108FailureReason.UnexpectedMessageReceived);
}
return {
serverName: payload.homeserver
};
} else {
// MSC4108-Flow: NewScanned - nothing to do
}
return {};
}
/**
* The second & third step in the OAuth2 QR login process.
* To be called after `negotiateProtocols` for the existing device.
* To be called after OAuth2 negotiation for the new device.
*
* @param input - Required for the new device to start the device authorization grant, not required for the existing device reciprocating the login
*/
async deviceAuthorizationGrant(input) {
if (this.isNewDevice) {
if (!input) {
throw new Error("Input must be provided for new device");
}
const {
metadata,
clientId,
deviceId
} = input;
const scope = generateScope(deviceId);
// MSC4108-Flow: NewDevice - start device authorization grant
const dagResponse = await startDeviceAuthorization({
clientId,
scope,
metadata
});
this.metadata = metadata;
this.grantInProgress = dagResponse;
const protocol = {
type: PayloadType.Protocol,
protocol: "device_authorization_grant",
device_id: deviceId,
device_authorization_grant: {
verification_uri: dagResponse.verification_uri,
verification_uri_complete: dagResponse.verification_uri_complete
}
};
await this.send(protocol);
return {
verificationUri: dagResponse.verification_uri_complete ?? dagResponse.verification_uri,
userCode: dagResponse.user_code
};
} else {
// The user needs to do step 7 for the out-of-band confirmation
// but, first we receive the protocol chosen by the other device so that
// the confirmation_uri is ready to go
logger.info("Waiting for protocol message");
const payload = await this.receive();
if (payload?.type === PayloadType.Failure) {
throw new RendezvousError("Failed", payload.reason);
}
if (payload?.type !== PayloadType.Protocol) {
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UnexpectedMessageReceived
});
throw new RendezvousError("Unexpected message received", MSC4108FailureReason.UnexpectedMessageReceived);
}
if (isDeviceAuthorizationGrantProtocolPayload(payload)) {
const {
device_authorization_grant: dag,
device_id: expectingNewDeviceId
} = payload;
const {
verification_uri: verificationUri,
verification_uri_complete: verificationUriComplete
} = dag;
let deviceAlreadyExists = true;
try {
await this.client?.getDevice(expectingNewDeviceId);
} catch (err) {
if (err instanceof MatrixError && err.httpStatus === 404) {
deviceAlreadyExists = false;
}
}
if (deviceAlreadyExists) {
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.DeviceAlreadyExists
});
throw new RendezvousError("Specified device ID already exists", MSC4108FailureReason.DeviceAlreadyExists);
}
this.expectingNewDeviceId = expectingNewDeviceId;
return {
verificationUri: verificationUriComplete ?? verificationUri
};
}
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UnsupportedProtocol
});
throw new RendezvousError("Received a request for an unsupported protocol", MSC4108FailureReason.UnsupportedProtocol);
}
}
/**
* The fourth step in the OAuth2 QR login process.
* The reciprocating device must perform step 5 for this method to resolve.
* To be called after {@link deviceAuthorizationGrant} only on the new device.
*/
async completeLoginOnNewDevice({
clientId
}) {
if (!this.isNewDevice || !this.grantInProgress || !this.metadata) {
throw new Error("Can only complete login on new device");
}
logger.info("Waiting for protocol accepted message");
// wait for accepted message
const payload = await this.receive();
if (!payload) {
throw new RendezvousError("No response from existing device", MSC4108FailureReason.UnexpectedMessageReceived);
}
if (payload.type === PayloadType.Failure) {
throw new RendezvousError("Failed", payload.reason);
}
if (payload.type !== PayloadType.ProtocolAccepted) {
throw new RendezvousError("Unexpected message received", MSC4108FailureReason.UnexpectedMessageReceived);
}
// poll for DAG
const res = await waitForDeviceAuthorization({
session: this.grantInProgress,
metadata: this.metadata,
clientId
});
if (!res) {
throw new RendezvousError("No response from device authorization endpoint", ClientRendezvousFailureReason.Unknown);
}
if ("error" in res) {
let reason = MSC4108FailureReason.UnexpectedMessageReceived;
if (res.error === "expired_token") {
reason = MSC4108FailureReason.AuthorizationExpired;
} else if (res.error === "access_denied") {
reason = MSC4108FailureReason.UserCancelled;
}
await this.send({
type: PayloadType.Failure,
reason
});
throw new RendezvousError("Rejection from device authorization endpoint", reason);
}
return res;
}
/**
* The fifth (and final) step in the OAuth2 QR login process.
* To be called after the new device has completed authentication.
*/
async shareSecrets() {
if (this.isNewDevice) {
await this.send({
type: PayloadType.Success
});
// then wait for secrets
logger.info("Waiting for secrets message");
const payload = await this.receive();
if (payload?.type === PayloadType.Failure) {
throw new RendezvousError("Failed", payload.reason);
}
if (payload?.type !== PayloadType.Secrets) {
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UnexpectedMessageReceived
});
throw new RendezvousError("Unexpected message received", MSC4108FailureReason.UnexpectedMessageReceived);
}
return {
secrets: payload
};
// then done?
} else {
if (!this.expectingNewDeviceId) {
throw new Error("No new device ID expected");
}
await this.send({
type: PayloadType.ProtocolAccepted
});
logger.info("Waiting for outcome message");
const payload = await this.receive();
if (payload?.type === PayloadType.Failure) {
throw new RendezvousError("Failed", payload.reason);
}
if (payload?.type === PayloadType.Declined) {
throw new RendezvousError("User declined", ClientRendezvousFailureReason.UserDeclined);
}
if (payload?.type !== PayloadType.Success) {
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UnexpectedMessageReceived
});
throw new RendezvousError("Unexpected message", MSC4108FailureReason.UnexpectedMessageReceived);
}
const timeout = Date.now() + 10000; // wait up to 10 seconds
do {
// is the device visible via the Homeserver?
try {
const device = await this.client?.getDevice(this.expectingNewDeviceId);
if (device) {
// if so, return the secrets
const secretsBundle = await this.client.getCrypto().exportSecretsBundle();
if (this.channel.cancelled) {
throw new RendezvousError("User cancelled", MSC4108FailureReason.UserCancelled);
}
// send secrets
await this.send(_objectSpread({
type: PayloadType.Secrets
}, secretsBundle));
return {
secrets: secretsBundle
};
// let the other side close the rendezvous session
}
} catch (err) {
if (err instanceof MatrixError && err.httpStatus === 404) {
// not found, so keep waiting until timeout
} else {
throw err;
}
}
await sleep(1000);
} while (Date.now() < timeout);
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.DeviceNotFound
});
throw new RendezvousError("New device not found", MSC4108FailureReason.DeviceNotFound);
}
}
async receive() {
return await this.channel.secureReceive();
}
async send(payload) {
await this.channel.secureSend(payload);
}
/**
* Decline the login on the existing device.
*/
async declineLoginOnExistingDevice() {
if (!this.isExistingDevice) {
throw new Error("Can only decline login on existing device");
}
await this.send({
type: PayloadType.Failure,
reason: MSC4108FailureReason.UserCancelled
});
}
/**
* Cancels the rendezvous session.
* @param reason the reason for the cancellation
*/
async cancel(reason) {
this.onFailure?.(reason);
await this.channel.cancel(reason);
}
/**
* Closes the rendezvous session.
*/
async close() {
await this.channel.close();
}
}
//# sourceMappingURL=MSC4108SignInWithQR.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
import { type RendezvousCode, type RendezvousIntent, type RendezvousFailureReason } from "./index.ts";
export interface RendezvousChannel<T> {
/**
* @returns the checksum/confirmation digits to be shown to the user
*/
connect(): Promise<string>;
/**
* Send a payload via the channel.
* @param data - payload to send
*/
send(data: T): Promise<void>;
/**
* Receive a payload from the channel.
* @returns the received payload
*/
receive(): Promise<Partial<T> | undefined>;
/**
* Close the channel and clear up any resources.
*/
close(): Promise<void>;
/**
* @returns a representation of the channel that can be encoded in a QR or similar
*/
generateCode(intent: RendezvousIntent): Promise<RendezvousCode>;
cancel(reason: RendezvousFailureReason): Promise<void>;
}
//# sourceMappingURL=RendezvousChannel.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousChannel.d.ts","sourceRoot":"","sources":["../../src/rendezvous/RendezvousChannel.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEtG,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAChC;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE3B;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAE3C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAEhE,MAAM,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D"}

View File

@@ -0,0 +1,3 @@
export {};
export {};
//# sourceMappingURL=RendezvousChannel.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousChannel.js","names":[],"sources":["../../src/rendezvous/RendezvousChannel.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\nimport { type RendezvousCode, type RendezvousIntent, type RendezvousFailureReason } from \"./index.ts\";\n\nexport interface RendezvousChannel<T> {\n /**\n * @returns the checksum/confirmation digits to be shown to the user\n */\n connect(): Promise<string>;\n\n /**\n * Send a payload via the channel.\n * @param data - payload to send\n */\n send(data: T): Promise<void>;\n\n /**\n * Receive a payload from the channel.\n * @returns the received payload\n */\n receive(): Promise<Partial<T> | undefined>;\n\n /**\n * Close the channel and clear up any resources.\n */\n close(): Promise<void>;\n\n /**\n * @returns a representation of the channel that can be encoded in a QR or similar\n */\n generateCode(intent: RendezvousIntent): Promise<RendezvousCode>;\n\n cancel(reason: RendezvousFailureReason): Promise<void>;\n}\n"],"mappings":"","ignoreList":[]}

View File

@@ -0,0 +1,9 @@
import { type RendezvousTransportDetails, type RendezvousIntent } from "./index.ts";
export interface RendezvousCode {
intent: RendezvousIntent;
rendezvous?: {
transport: RendezvousTransportDetails;
algorithm: string;
};
}
//# sourceMappingURL=RendezvousCode.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousCode.d.ts","sourceRoot":"","sources":["../../src/rendezvous/RendezvousCode.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,0BAA0B,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE;QACT,SAAS,EAAE,0BAA0B,CAAC;QACtC,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;CACL"}

View File

@@ -0,0 +1,3 @@
export {};
export {};
//# sourceMappingURL=RendezvousCode.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousCode.js","names":[],"sources":["../../src/rendezvous/RendezvousCode.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\nimport { type RendezvousTransportDetails, type RendezvousIntent } from \"./index.ts\";\n\nexport interface RendezvousCode {\n intent: RendezvousIntent;\n rendezvous?: {\n transport: RendezvousTransportDetails;\n algorithm: string;\n };\n}\n"],"mappings":"","ignoreList":[]}

View File

@@ -0,0 +1,6 @@
import { type RendezvousFailureReason } from "./index.ts";
export declare class RendezvousError extends Error {
readonly code: RendezvousFailureReason;
constructor(message: string, code: RendezvousFailureReason);
}
//# sourceMappingURL=RendezvousError.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousError.d.ts","sourceRoot":"","sources":["../../src/rendezvous/RendezvousError.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,qBAAa,eAAgB,SAAQ,KAAK;aAGlB,IAAI,EAAE,uBAAuB;IAFjD,YACI,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,uBAAuB,EAGhD;CACJ"}

View File

@@ -0,0 +1,23 @@
/*
Copyright 2022 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.
*/
export class RendezvousError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
//# sourceMappingURL=RendezvousError.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousError.js","names":[],"sources":["../../src/rendezvous/RendezvousError.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\nimport { type RendezvousFailureReason } from \"./index.ts\";\n\nexport class RendezvousError extends Error {\n public constructor(\n message: string,\n public readonly code: RendezvousFailureReason,\n ) {\n super(message);\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA,OAAO,MAAM,eAAe,SAAS,KAAK,CAAC;EAChC,WAAW,CACd,OAAe,EACC,IAA6B,EAC/C;IACE,KAAK,CAAC,OAAO,CAAC;IAAC,KAFC,IAA6B,GAA7B,IAA6B;EAGjD;AACJ","ignoreList":[]}

View File

@@ -0,0 +1,31 @@
export type RendezvousFailureListener = (reason: RendezvousFailureReason) => void;
export type RendezvousFailureReason = MSC4108FailureReason | ClientRendezvousFailureReason;
export declare enum MSC4108FailureReason {
AuthorizationExpired = "authorization_expired",
DeviceAlreadyExists = "device_already_exists",
DeviceNotFound = "device_not_found",
UnexpectedMessageReceived = "unexpected_message_received",
UnsupportedProtocol = "unsupported_protocol",
UserCancelled = "user_cancelled"
}
export declare enum ClientRendezvousFailureReason {
/** The sign in request has expired */
Expired = "expired",
/** The homeserver is lacking support for the required features */
HomeserverLacksSupport = "homeserver_lacks_support",
/** The secure channel verification failed meaning that it might be compromised */
InsecureChannelDetected = "insecure_channel_detected",
/** An invalid/incompatible QR code was scanned */
InvalidCode = "invalid_code",
/** The other device is not signed in */
OtherDeviceNotSignedIn = "other_device_not_signed_in",
/** The other device is already signed in */
OtherDeviceAlreadySignedIn = "other_device_already_signed_in",
/** Other */
Unknown = "unknown",
/** The user declined the sign in request */
UserDeclined = "user_declined",
/** The rendezvous request is missing an ETag header */
ETagMissing = "etag_missing"
}
//# sourceMappingURL=RendezvousFailureReason.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousFailureReason.d.ts","sourceRoot":"","sources":["../../src/rendezvous/RendezvousFailureReason.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,yBAAyB,GAAG,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;AAElF,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,GAAG,6BAA6B,CAAC;AAE3F,oBAAY,oBAAoB;IAC5B,oBAAoB,0BAA0B;IAC9C,mBAAmB,0BAA0B;IAC7C,cAAc,qBAAqB;IACnC,yBAAyB,gCAAgC;IACzD,mBAAmB,yBAAyB;IAC5C,aAAa,mBAAmB;CACnC;AAED,oBAAY,6BAA6B;IACrC,sCAAsC;IACtC,OAAO,YAAY;IACnB,kEAAkE;IAClE,sBAAsB,6BAA6B;IACnD,kFAAkF;IAClF,uBAAuB,8BAA8B;IACrD,kDAAkD;IAClD,WAAW,iBAAiB;IAC5B,wCAAwC;IACxC,sBAAsB,+BAA+B;IACrD,4CAA4C;IAC5C,0BAA0B,mCAAmC;IAC7D,YAAY;IACZ,OAAO,YAAY;IACnB,4CAA4C;IAC5C,YAAY,kBAAkB;IAC9B,uDAAuD;IACvD,WAAW,iBAAiB;CAC/B"}

View File

@@ -0,0 +1,47 @@
/*
Copyright 2022 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.
*/
export let MSC4108FailureReason = /*#__PURE__*/function (MSC4108FailureReason) {
MSC4108FailureReason["AuthorizationExpired"] = "authorization_expired";
MSC4108FailureReason["DeviceAlreadyExists"] = "device_already_exists";
MSC4108FailureReason["DeviceNotFound"] = "device_not_found";
MSC4108FailureReason["UnexpectedMessageReceived"] = "unexpected_message_received";
MSC4108FailureReason["UnsupportedProtocol"] = "unsupported_protocol";
MSC4108FailureReason["UserCancelled"] = "user_cancelled";
return MSC4108FailureReason;
}({});
export let ClientRendezvousFailureReason = /*#__PURE__*/function (ClientRendezvousFailureReason) {
/** The sign in request has expired */
ClientRendezvousFailureReason["Expired"] = "expired";
/** The homeserver is lacking support for the required features */
ClientRendezvousFailureReason["HomeserverLacksSupport"] = "homeserver_lacks_support";
/** The secure channel verification failed meaning that it might be compromised */
ClientRendezvousFailureReason["InsecureChannelDetected"] = "insecure_channel_detected";
/** An invalid/incompatible QR code was scanned */
ClientRendezvousFailureReason["InvalidCode"] = "invalid_code";
/** The other device is not signed in */
ClientRendezvousFailureReason["OtherDeviceNotSignedIn"] = "other_device_not_signed_in";
/** The other device is already signed in */
ClientRendezvousFailureReason["OtherDeviceAlreadySignedIn"] = "other_device_already_signed_in";
/** Other */
ClientRendezvousFailureReason["Unknown"] = "unknown";
/** The user declined the sign in request */
ClientRendezvousFailureReason["UserDeclined"] = "user_declined";
/** The rendezvous request is missing an ETag header */
ClientRendezvousFailureReason["ETagMissing"] = "etag_missing";
return ClientRendezvousFailureReason;
}({});
//# sourceMappingURL=RendezvousFailureReason.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousFailureReason.js","names":[],"sources":["../../src/rendezvous/RendezvousFailureReason.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 type RendezvousFailureListener = (reason: RendezvousFailureReason) => void;\n\nexport type RendezvousFailureReason = MSC4108FailureReason | ClientRendezvousFailureReason;\n\nexport enum MSC4108FailureReason {\n AuthorizationExpired = \"authorization_expired\",\n DeviceAlreadyExists = \"device_already_exists\",\n DeviceNotFound = \"device_not_found\",\n UnexpectedMessageReceived = \"unexpected_message_received\",\n UnsupportedProtocol = \"unsupported_protocol\",\n UserCancelled = \"user_cancelled\",\n}\n\nexport enum ClientRendezvousFailureReason {\n /** The sign in request has expired */\n Expired = \"expired\",\n /** The homeserver is lacking support for the required features */\n HomeserverLacksSupport = \"homeserver_lacks_support\",\n /** The secure channel verification failed meaning that it might be compromised */\n InsecureChannelDetected = \"insecure_channel_detected\",\n /** An invalid/incompatible QR code was scanned */\n InvalidCode = \"invalid_code\",\n /** The other device is not signed in */\n OtherDeviceNotSignedIn = \"other_device_not_signed_in\",\n /** The other device is already signed in */\n OtherDeviceAlreadySignedIn = \"other_device_already_signed_in\",\n /** Other */\n Unknown = \"unknown\",\n /** The user declined the sign in request */\n UserDeclined = \"user_declined\",\n /** The rendezvous request is missing an ETag header */\n ETagMissing = \"etag_missing\",\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA,WAAY,oBAAoB,0BAApB,oBAAoB;EAApB,oBAAoB;EAApB,oBAAoB;EAApB,oBAAoB;EAApB,oBAAoB;EAApB,oBAAoB;EAApB,oBAAoB;EAAA,OAApB,oBAAoB;AAAA;AAShC,WAAY,6BAA6B,0BAA7B,6BAA6B;EACrC;EADQ,6BAA6B;EAGrC;EAHQ,6BAA6B;EAKrC;EALQ,6BAA6B;EAOrC;EAPQ,6BAA6B;EASrC;EATQ,6BAA6B;EAWrC;EAXQ,6BAA6B;EAarC;EAbQ,6BAA6B;EAerC;EAfQ,6BAA6B;EAiBrC;EAjBQ,6BAA6B;EAAA,OAA7B,6BAA6B;AAAA","ignoreList":[]}

View File

@@ -0,0 +1,5 @@
export declare enum RendezvousIntent {
LOGIN_ON_NEW_DEVICE = "login.start",
RECIPROCATE_LOGIN_ON_EXISTING_DEVICE = "login.reciprocate"
}
//# sourceMappingURL=RendezvousIntent.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousIntent.d.ts","sourceRoot":"","sources":["../../src/rendezvous/RendezvousIntent.ts"],"names":[],"mappings":"AAgBA,oBAAY,gBAAgB;IACxB,mBAAmB,gBAAgB;IACnC,oCAAoC,sBAAsB;CAC7D"}

View File

@@ -0,0 +1,22 @@
/*
Copyright 2022 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.
*/
export let RendezvousIntent = /*#__PURE__*/function (RendezvousIntent) {
RendezvousIntent["LOGIN_ON_NEW_DEVICE"] = "login.start";
RendezvousIntent["RECIPROCATE_LOGIN_ON_EXISTING_DEVICE"] = "login.reciprocate";
return RendezvousIntent;
}({});
//# sourceMappingURL=RendezvousIntent.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousIntent.js","names":[],"sources":["../../src/rendezvous/RendezvousIntent.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 enum RendezvousIntent {\n LOGIN_ON_NEW_DEVICE = \"login.start\",\n RECIPROCATE_LOGIN_ON_EXISTING_DEVICE = \"login.reciprocate\",\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,WAAY,gBAAgB,0BAAhB,gBAAgB;EAAhB,gBAAgB;EAAhB,gBAAgB;EAAA,OAAhB,gBAAgB;AAAA","ignoreList":[]}

View File

@@ -0,0 +1,36 @@
import { type RendezvousFailureListener, type RendezvousFailureReason } from "./index.ts";
export interface RendezvousTransportDetails {
type: string;
}
/**
* Interface representing a generic rendezvous transport.
*/
export interface RendezvousTransport<T> {
/**
* Ready state of the transport. This is set to true when the transport is ready to be used.
*/
readonly ready: boolean;
/**
* Listener for cancellation events. This is called when the rendezvous is cancelled or fails.
*/
onFailure?: RendezvousFailureListener;
/**
* @returns the transport details that can be encoded in a QR or similar
*/
details(): Promise<RendezvousTransportDetails>;
/**
* Send data via the transport.
* @param data - the data itself
*/
send(data: T): Promise<void>;
/**
* Receive data from the transport.
*/
receive(): Promise<Partial<T> | undefined>;
/**
* Cancel the rendezvous. This will call `onCancelled()` if it is set.
* @param reason - the reason for the cancellation/failure
*/
cancel(reason: RendezvousFailureReason): Promise<void>;
}
//# sourceMappingURL=RendezvousTransport.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousTransport.d.ts","sourceRoot":"","sources":["../../src/rendezvous/RendezvousTransport.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1F,MAAM,WAAW,0BAA0B;IACvC,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IAEtC;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAE/C;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAE3C;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D"}

View File

@@ -0,0 +1,3 @@
export {};
export {};
//# sourceMappingURL=RendezvousTransport.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RendezvousTransport.js","names":[],"sources":["../../src/rendezvous/RendezvousTransport.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\nimport { type RendezvousFailureListener, type RendezvousFailureReason } from \"./index.ts\";\n\nexport interface RendezvousTransportDetails {\n type: string;\n}\n\n/**\n * Interface representing a generic rendezvous transport.\n */\nexport interface RendezvousTransport<T> {\n /**\n * Ready state of the transport. This is set to true when the transport is ready to be used.\n */\n readonly ready: boolean;\n\n /**\n * Listener for cancellation events. This is called when the rendezvous is cancelled or fails.\n */\n onFailure?: RendezvousFailureListener;\n\n /**\n * @returns the transport details that can be encoded in a QR or similar\n */\n details(): Promise<RendezvousTransportDetails>;\n\n /**\n * Send data via the transport.\n * @param data - the data itself\n */\n send(data: T): Promise<void>;\n\n /**\n * Receive data from the transport.\n */\n receive(): Promise<Partial<T> | undefined>;\n\n /**\n * Cancel the rendezvous. This will call `onCancelled()` if it is set.\n * @param reason - the reason for the cancellation/failure\n */\n cancel(reason: RendezvousFailureReason): Promise<void>;\n}\n"],"mappings":"","ignoreList":[]}

View File

@@ -0,0 +1,58 @@
import { type Curve25519PublicKey, QrCodeIntent } from "@matrix-org/matrix-sdk-crypto-wasm";
import { ClientRendezvousFailureReason, MSC4108FailureReason, type MSC4108Payload, type RendezvousFailureListener } from "../index.ts";
import { type MSC4108RendezvousSession } from "../transports/MSC4108RendezvousSession.ts";
/**
* Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* secure rendezvous session protocol.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
* Imports @matrix-org/matrix-sdk-crypto-wasm so should be async-imported to avoid bundling the WASM into the main bundle.
*/
export declare class MSC4108SecureChannel {
private rendezvousSession;
private theirPublicKey?;
onFailure?: RendezvousFailureListener | undefined;
private readonly secureChannel;
private establishedChannel?;
private connected;
constructor(rendezvousSession: MSC4108RendezvousSession, theirPublicKey?: Curve25519PublicKey | undefined, onFailure?: RendezvousFailureListener | undefined);
/**
* Generate a QR code for the current session.
* @param mode the mode to generate the QR code in, either `Login` or `Reciprocate`.
* @param serverName the name of the homeserver to connect to, as defined by server discovery in the spec, required for `Reciprocate` mode.
*/
generateCode(mode: QrCodeIntent.Login): Promise<Uint8Array>;
generateCode(mode: QrCodeIntent.Reciprocate, serverName: string): Promise<Uint8Array>;
/**
* Returns the check code for the secure channel or undefined if not generated yet.
*/
getCheckCode(): string | undefined;
/**
* Connects and establishes a secure channel with the other device.
*/
connect(): Promise<void>;
private decrypt;
private encrypt;
/**
* Sends a payload securely to the other device.
* @param payload the payload to encrypt and send
*/
secureSend<T extends MSC4108Payload>(payload: T): Promise<void>;
/**
* Receives an encrypted payload from the other device and decrypts it.
*/
secureReceive<T extends MSC4108Payload>(): Promise<Partial<T> | undefined>;
/**
* Closes the secure channel.
*/
close(): Promise<void>;
/**
* Cancels the secure channel.
* @param reason the reason for the cancellation
*/
cancel(reason: MSC4108FailureReason | ClientRendezvousFailureReason): Promise<void>;
/**
* Returns whether the rendezvous session has been cancelled.
*/
get cancelled(): boolean;
}
//# sourceMappingURL=MSC4108SecureChannel.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MSC4108SecureChannel.d.ts","sourceRoot":"","sources":["../../../src/rendezvous/channels/MSC4108SecureChannel.ts"],"names":[],"mappings":"AAgBA,OAAO,EACH,KAAK,mBAAmB,EAIxB,YAAY,EACf,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EACH,6BAA6B,EAC7B,oBAAoB,EACpB,KAAK,cAAc,EAEnB,KAAK,yBAAyB,EACjC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AAG1F;;;;;GAKG;AACH,qBAAa,oBAAoB;IAMzB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,cAAc,CAAC;IAChB,SAAS,CAAC,EAAE,yBAAyB;IAPhD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,kBAAkB,CAAC,CAAmB;IAC9C,OAAO,CAAC,SAAS,CAAS;IAE1B,YACY,iBAAiB,EAAE,wBAAwB,EAC3C,cAAc,CAAC,EAAE,mBAAmB,YAAA,EACrC,SAAS,CAAC,EAAE,yBAAyB,YAAA,EAG/C;IAED;;;;OAIG;IACU,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAenG;;OAEG;IACI,YAAY,IAAI,MAAM,GAAG,SAAS,CASxC;IAED;;OAEG;IACU,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAuGpC;YAEa,OAAO;YAQP,OAAO;IAQrB;;;OAGG;IACU,UAAU,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3E;IAED;;OAEG;IACU,aAAa,CAAC,CAAC,SAAS,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CActF;IAED;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAElC;IAED;;;OAGG;IACU,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAY/F;IAED;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;CACJ"}

View File

@@ -0,0 +1,221 @@
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 { Ecies, QrCodeData, QrCodeIntent } from "@matrix-org/matrix-sdk-crypto-wasm";
import { ClientRendezvousFailureReason, MSC4108FailureReason, RendezvousError } from "../index.js";
import { logger } from "../../logger.js";
/**
* Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
* secure rendezvous session protocol.
* @experimental Note that this is UNSTABLE and may have breaking changes without notice.
* Imports @matrix-org/matrix-sdk-crypto-wasm so should be async-imported to avoid bundling the WASM into the main bundle.
*/
export class MSC4108SecureChannel {
constructor(rendezvousSession, theirPublicKey, onFailure) {
_defineProperty(this, "secureChannel", void 0);
_defineProperty(this, "establishedChannel", void 0);
_defineProperty(this, "connected", false);
this.rendezvousSession = rendezvousSession;
this.theirPublicKey = theirPublicKey;
this.onFailure = onFailure;
this.secureChannel = new Ecies();
}
/**
* Generate a QR code for the current session.
* @param mode the mode to generate the QR code in, either `Login` or `Reciprocate`.
* @param serverName the name of the homeserver to connect to, as defined by server discovery in the spec, required for `Reciprocate` mode.
*/
async generateCode(mode, serverName) {
const {
url
} = this.rendezvousSession;
if (!url) {
throw new Error("No rendezvous session URL");
}
return new QrCodeData(this.secureChannel.public_key(), url, mode === QrCodeIntent.Reciprocate ? serverName : undefined).toBytes();
}
/**
* Returns the check code for the secure channel or undefined if not generated yet.
*/
getCheckCode() {
const x = this.establishedChannel?.check_code();
if (!x) {
return undefined;
}
return Array.from(x.as_bytes()).map(b => `${b % 10}`).join("");
}
/**
* Connects and establishes a secure channel with the other device.
*/
async connect() {
if (this.connected) {
throw new Error("Channel already connected");
}
if (this.theirPublicKey) {
// We are the scanning device
const result = this.secureChannel.establish_outbound_channel(this.theirPublicKey, "MATRIX_QR_CODE_LOGIN_INITIATE");
this.establishedChannel = result.channel;
/*
Secure Channel step 4. Device S sends the initial message
Nonce := 0
SH := ECDH(Ss, Gp)
EncKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN|" || Gp || "|" || Sp, 0, 32)
TaggedCiphertext := ChaCha20Poly1305_Encrypt(EncKey, Nonce, "MATRIX_QR_CODE_LOGIN_INITIATE")
Nonce := Nonce + 2
LoginInitiateMessage := UnpaddedBase64(TaggedCiphertext) || "|" || UnpaddedBase64(Sp)
*/
{
logger.info("Sending LoginInitiateMessage");
await this.rendezvousSession.send(result.initial_message);
}
/*
Secure Channel step 6. Verification by Device S
Nonce_G := 1
(TaggedCiphertext, Sp) := Unpack(Message)
Plaintext := ChaCha20Poly1305_Decrypt(EncKey, Nonce_G, TaggedCiphertext)
Nonce_G := Nonce_G + 2
unless Plaintext == "MATRIX_QR_CODE_LOGIN_OK":
FAIL
*/
{
logger.info("Waiting for LoginOkMessage");
const ciphertext = await this.rendezvousSession.receive();
if (!ciphertext) {
throw new RendezvousError("No response from other device", MSC4108FailureReason.UnexpectedMessageReceived);
}
const candidateLoginOkMessage = await this.decrypt(ciphertext);
if (candidateLoginOkMessage !== "MATRIX_QR_CODE_LOGIN_OK") {
throw new RendezvousError("Invalid response from other device", ClientRendezvousFailureReason.InsecureChannelDetected);
}
// Step 6 is now complete. We trust the channel
}
} else {
/*
Secure Channel step 5. Device G confirms
Nonce_S := 0
(TaggedCiphertext, Sp) := Unpack(LoginInitiateMessage)
SH := ECDH(Gs, Sp)
EncKey := HKDF_SHA256(SH, "MATRIX_QR_CODE_LOGIN|" || Gp || "|" || Sp, 0, 32)
Plaintext := ChaCha20Poly1305_Decrypt(EncKey, Nonce_S, TaggedCiphertext)
Nonce_S := Nonce_S + 2
*/
// wait for the other side to send us their public key
logger.info("Waiting for LoginInitiateMessage");
const loginInitiateMessage = await this.rendezvousSession.receive();
if (!loginInitiateMessage) {
throw new RendezvousError("No response from other device", MSC4108FailureReason.UnexpectedMessageReceived);
}
const {
channel,
message: candidateLoginInitiateMessage
} = this.secureChannel.establish_inbound_channel(loginInitiateMessage);
this.establishedChannel = channel;
if (candidateLoginInitiateMessage !== "MATRIX_QR_CODE_LOGIN_INITIATE") {
throw new RendezvousError("Invalid response from other device", ClientRendezvousFailureReason.InsecureChannelDetected);
}
logger.info("LoginInitiateMessage received");
logger.info("Sending LoginOkMessage");
const loginOkMessage = await this.encrypt("MATRIX_QR_CODE_LOGIN_OK");
await this.rendezvousSession.send(loginOkMessage);
// Step 5 is complete. We don't yet trust the channel
// next step will be for the user to confirm the check code on the other device
}
this.connected = true;
}
async decrypt(ciphertext) {
if (!this.establishedChannel) {
throw new Error("Channel closed");
}
return this.establishedChannel.decrypt(ciphertext);
}
async encrypt(plaintext) {
if (!this.establishedChannel) {
throw new Error("Channel closed");
}
return this.establishedChannel.encrypt(plaintext);
}
/**
* Sends a payload securely to the other device.
* @param payload the payload to encrypt and send
*/
async secureSend(payload) {
if (!this.connected) {
throw new Error("Channel closed");
}
const stringifiedPayload = JSON.stringify(payload);
logger.debug(`=> {"type": ${JSON.stringify(payload.type)}, ...}`);
await this.rendezvousSession.send(await this.encrypt(stringifiedPayload));
}
/**
* Receives an encrypted payload from the other device and decrypts it.
*/
async secureReceive() {
if (!this.establishedChannel) {
throw new Error("Channel closed");
}
const ciphertext = await this.rendezvousSession.receive();
if (!ciphertext) {
return undefined;
}
const plaintext = await this.decrypt(ciphertext);
const json = JSON.parse(plaintext);
logger.debug(`<= {"type": ${JSON.stringify(json.type)}, ...}`);
return json;
}
/**
* Closes the secure channel.
*/
async close() {
await this.rendezvousSession.close();
}
/**
* Cancels the secure channel.
* @param reason the reason for the cancellation
*/
async cancel(reason) {
try {
await this.rendezvousSession.cancel(reason);
this.onFailure?.(reason);
} finally {
if (reason !== ClientRendezvousFailureReason.UserDeclined && reason !== MSC4108FailureReason.UserCancelled) {
await this.close();
}
}
}
/**
* Returns whether the rendezvous session has been cancelled.
*/
get cancelled() {
return this.rendezvousSession.cancelled;
}
}
//# sourceMappingURL=MSC4108SecureChannel.js.map

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rendezvous/channels/index.ts"],"names":[],"mappings":"AAgBA,cAAc,2BAA2B,CAAC"}

View File

@@ -0,0 +1,18 @@
/*
Copyright 2022 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.
*/
export * from "./MSC4108SecureChannel.js";
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","names":[],"sources":["../../../src/rendezvous/channels/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 \"./MSC4108SecureChannel.ts\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,cAAc,2BAA2B","ignoreList":[]}

46
node_modules/matrix-js-sdk/lib/rendezvous/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,46 @@
import { type MatrixClient } from "../matrix.ts";
import { type RendezvousFailureListener } from "./RendezvousFailureReason.ts";
import { MSC4108SignInWithQR } from "./MSC4108SignInWithQR.ts";
export * from "./MSC4108SignInWithQR.ts";
export type * from "./RendezvousChannel.ts";
export type * from "./RendezvousCode.ts";
export * from "./RendezvousError.ts";
export * from "./RendezvousFailureReason.ts";
export * from "./RendezvousIntent.ts";
export type * from "./RendezvousTransport.ts";
export * from "./transports/index.ts";
export * from "./channels/index.ts";
/**
* Check if the homeserver that the client is connected to supports a variant of sign-in with QR that we can use.
*
* @param client the client to check for sign-in with QR support
* @returns true if the homeserver that the client is connected to supports a variant of sign-in with QR that we can use, false otherwise.
*/
export declare function isSignInWithQRAvailable(client: MatrixClient): Promise<boolean>;
/**
* Start a linking flow from an existing authenticated client by generating a QR code that can be scanned by the new device.
* The new device will then authenticate with the server and link itself to the same account as the existing client and
* share the end-to-end encryption keys.
*
* @param client the existing client
* @param onFailure callback for when the linking process fails
* @param abortSignal an AbortSignal that can be used to cancel the linking process,
* for example when the user cancels out of the flow.
* This will unbind the {@link onFailure} callback and prevent any further steps in the flow from being executed.
* @returns a promise that resolves to an instance of the linking flow
*/
export declare function linkNewDeviceByGeneratingQR(client: MatrixClient, onFailure: RendezvousFailureListener, abortSignal: AbortSignal): Promise<MSC4108SignInWithQR>;
/**
* Start a sign-in flow by generating a QR code that can be scanned by an existing authenticated client.
* The existing client will then help complete the authentication of the new device and link it to the same account,
* sharing the end-to-end encryption keys.
*
* @param tempClient temporary client used during the flow for the rendezvous channel
* @param onFailure callback for when the sign-in process fails
* @param abortSignal an AbortSignal that can be used to cancel the linking process,
* for example when the user cancels out of the flow.
* This will unbind the {@link onFailure} callback and prevent any further steps in the flow from being executed.
* @returns a promise that resolves to an instance of the sign-in flow
*/
export declare function signInByGeneratingQR(tempClient: MatrixClient, onFailure: RendezvousFailureListener, abortSignal: AbortSignal): Promise<MSC4108SignInWithQR>;
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rendezvous/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,YAAY,EAA8C,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAwB,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACpG,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,cAAc,0BAA0B,CAAC;AACzC,mBAAmB,wBAAwB,CAAC;AAC5C,mBAAmB,qBAAqB,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,mBAAmB,0BAA0B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AAEpC;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,2BAA2B,CAC7C,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,yBAAyB,EACpC,WAAW,EAAE,WAAW,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAG9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACtC,UAAU,EAAE,YAAY,EACxB,SAAS,EAAE,yBAAyB,EACpC,WAAW,EAAE,WAAW,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAM9B"}

113
node_modules/matrix-js-sdk/lib/rendezvous/index.js generated vendored Normal file
View File

@@ -0,0 +1,113 @@
/*
Copyright 2022 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 { OAuthGrantType } from "../matrix.js";
import { MSC4108FailureReason } from "./RendezvousFailureReason.js";
import { MSC4108SignInWithQR } from "./MSC4108SignInWithQR.js";
import { MSC4108RendezvousSession } from "./transports/MSC4108RendezvousSession.js";
import { MSC4108SecureChannel } from "./channels/MSC4108SecureChannel.js";
import { RendezvousIntent } from "./RendezvousIntent.js";
import { logger } from "../logger.js";
export * from "./MSC4108SignInWithQR.js";
export * from "./RendezvousError.js";
export * from "./RendezvousFailureReason.js";
export * from "./RendezvousIntent.js";
export * from "./transports/index.js";
export * from "./channels/index.js";
/**
* Check if the homeserver that the client is connected to supports a variant of sign-in with QR that we can use.
*
* @param client the client to check for sign-in with QR support
* @returns true if the homeserver that the client is connected to supports a variant of sign-in with QR that we can use, false otherwise.
*/
export async function isSignInWithQRAvailable(client) {
let metadata;
try {
metadata = await client.getAuthMetadata();
} catch (e) {
logger.warn("Failed to fetch auth metadata, assuming sign-in with QR is unavailable", e);
return false;
}
// check for support of device authorization grant
if (!metadata.grant_types_supported.includes(OAuthGrantType.DeviceAuthorization)) {
return false;
}
// check for unstable support for MSC4108 2024 version
return client.doesServerSupportUnstableFeature("org.matrix.msc4108");
}
/**
* Start a linking flow from an existing authenticated client by generating a QR code that can be scanned by the new device.
* The new device will then authenticate with the server and link itself to the same account as the existing client and
* share the end-to-end encryption keys.
*
* @param client the existing client
* @param onFailure callback for when the linking process fails
* @param abortSignal an AbortSignal that can be used to cancel the linking process,
* for example when the user cancels out of the flow.
* This will unbind the {@link onFailure} callback and prevent any further steps in the flow from being executed.
* @returns a promise that resolves to an instance of the linking flow
*/
export async function linkNewDeviceByGeneratingQR(client, onFailure, abortSignal) {
// we assume rust crypto is already initialised
return initGenerateQrFlow(RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE, client, onFailure, abortSignal);
}
/**
* Start a sign-in flow by generating a QR code that can be scanned by an existing authenticated client.
* The existing client will then help complete the authentication of the new device and link it to the same account,
* sharing the end-to-end encryption keys.
*
* @param tempClient temporary client used during the flow for the rendezvous channel
* @param onFailure callback for when the sign-in process fails
* @param abortSignal an AbortSignal that can be used to cancel the linking process,
* for example when the user cancels out of the flow.
* This will unbind the {@link onFailure} callback and prevent any further steps in the flow from being executed.
* @returns a promise that resolves to an instance of the sign-in flow
*/
export async function signInByGeneratingQR(tempClient, onFailure, abortSignal) {
// ensure rust crypto is initialized as needed for the secure channel
const RustSdkCryptoJs = await import("@matrix-org/matrix-sdk-crypto-wasm");
await RustSdkCryptoJs.initAsync();
return initGenerateQrFlow(RendezvousIntent.LOGIN_ON_NEW_DEVICE, tempClient, onFailure, abortSignal);
}
async function initGenerateQrFlow(intent, client, onFailure, abortSignal) {
const session = new MSC4108RendezvousSession({
onFailure,
client
});
const channel = new MSC4108SecureChannel(session, undefined, onFailure);
const flow = new MSC4108SignInWithQR(channel, false, intent === RendezvousIntent.LOGIN_ON_NEW_DEVICE ? undefined : client, onFailure);
if (abortSignal.aborted) return flow;
abortSignal.onabort = () => {
// Detach failure handlers
session.onFailure = undefined;
channel.onFailure = undefined;
flow.onFailure = undefined;
// Cancel the session
void flow.cancel(MSC4108FailureReason.UserCancelled);
};
await session.send(""); // open channel
if (!abortSignal.aborted) {
await flow.generateCode();
}
return flow;
}
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,61 @@
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

@@ -0,0 +1 @@
{"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

@@ -0,0 +1,234 @@
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

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

View File

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

View File

@@ -0,0 +1,18 @@
/*
Copyright 2022 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.
*/
export * from "./MSC4108RendezvousSession.js";
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +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":[]}