167 lines
4.5 KiB
TypeScript
167 lines
4.5 KiB
TypeScript
// Copyright (C) 2026 Whiterun LLC,
|
|
// This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later.
|
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/lgpl-3.0.html
|
|
|
|
import { RelayClient } from "./relay-client.js";
|
|
import {
|
|
createConnectionManager,
|
|
VisibilityChangeContext,
|
|
} from "./connection-manager.js";
|
|
import { debug, error as logError, Scope } from "./log.js";
|
|
|
|
type RelayStatusCode =
|
|
| "connected"
|
|
| "reconnecting"
|
|
| "disconnected"
|
|
| "session_deleted";
|
|
|
|
export class RelayStatus {
|
|
status: RelayStatusCode;
|
|
error: string | null;
|
|
sessionId: string | null;
|
|
|
|
private constructor(
|
|
status: RelayStatusCode,
|
|
error: string | null,
|
|
sessionId: string | null = null,
|
|
) {
|
|
this.status = status;
|
|
this.error = error;
|
|
this.sessionId = sessionId;
|
|
}
|
|
|
|
static connected(sessionId?: string): RelayStatus {
|
|
return new RelayStatus("connected", null, sessionId || null);
|
|
}
|
|
|
|
static reconnecting(reason: string | null, sessionId?: string): RelayStatus {
|
|
return new RelayStatus("reconnecting", reason, sessionId || null);
|
|
}
|
|
|
|
static disconnected(): RelayStatus {
|
|
return new RelayStatus("disconnected", null, null);
|
|
}
|
|
|
|
static sessionDeleted(): RelayStatus {
|
|
return new RelayStatus("session_deleted", null, null);
|
|
}
|
|
}
|
|
|
|
export interface RelayUpdatePayload {
|
|
client: RelayClient;
|
|
status: RelayStatus;
|
|
}
|
|
|
|
export type RelayStatusCallback = (_payload: RelayUpdatePayload) => void;
|
|
|
|
export const initiateRelay = (
|
|
dispatchCallback: RelayStatusCallback,
|
|
signerPrivateKey: Uint8Array,
|
|
pairPublicKey: Uint8Array,
|
|
options?: {
|
|
explicitRelayUrls: string[];
|
|
reconnectInterval?: number;
|
|
maxReconnectAttempts?: number;
|
|
enableVisibilityHandling?: boolean;
|
|
},
|
|
) => {
|
|
const client = new RelayClient({
|
|
explicitRelayUrls: options?.explicitRelayUrls ?? [],
|
|
signerPrivateKey: signerPrivateKey,
|
|
pairedPublicKey: pairPublicKey,
|
|
});
|
|
|
|
let lastProcessedTimestamp: number = 0;
|
|
|
|
const connectionManager = createConnectionManager(
|
|
client,
|
|
{
|
|
onConnected: () => {
|
|
if (lastProcessedTimestamp > 0) {
|
|
client.setLastProcessedTimestamp(lastProcessedTimestamp);
|
|
} else {
|
|
lastProcessedTimestamp = client.getLastProcessedTimestamp();
|
|
}
|
|
dispatchCallback({
|
|
client,
|
|
status: RelayStatus.connected(),
|
|
});
|
|
},
|
|
onReconnecting: (_client: RelayClient, reason: string | null) => {
|
|
dispatchCallback({
|
|
client,
|
|
status: RelayStatus.reconnecting(reason),
|
|
});
|
|
},
|
|
onDisconnected: () => {
|
|
dispatchCallback({
|
|
client,
|
|
status: RelayStatus.disconnected(),
|
|
});
|
|
},
|
|
onError: (_client: RelayClient, _error: any) => {
|
|
// Error handling is already done in onReconnecting
|
|
},
|
|
},
|
|
{
|
|
connected: "connection",
|
|
disconnected: "disconnect",
|
|
error: "error",
|
|
},
|
|
{
|
|
reconnectInterval: options?.reconnectInterval,
|
|
maxReconnectAttempts: options?.maxReconnectAttempts,
|
|
enableVisibilityHandling: options?.enableVisibilityHandling ?? true,
|
|
scope: Scope.Relay,
|
|
onVisibilityChange: async (
|
|
context: VisibilityChangeContext<RelayClient>,
|
|
) => {
|
|
if (context.state === "hidden") {
|
|
debug(Scope.Relay, "Page hidden, disconnecting relay");
|
|
context.setPaused(true);
|
|
try {
|
|
await client.disconnect();
|
|
lastProcessedTimestamp = client.getLastProcessedTimestamp();
|
|
debug(
|
|
Scope.Relay,
|
|
`Disconnected, last processed timestamp: ${lastProcessedTimestamp}`,
|
|
);
|
|
dispatchCallback({
|
|
client,
|
|
status: RelayStatus.disconnected(),
|
|
});
|
|
} catch (error) {
|
|
logError(
|
|
Scope.Relay,
|
|
"Error disconnecting on visibility change:",
|
|
error,
|
|
);
|
|
}
|
|
} else if (context.state === "visible") {
|
|
if (context.isPaused()) {
|
|
debug(Scope.Relay, "Page visible, reconnecting relay");
|
|
context.setPaused(false);
|
|
context.startConnectionLoop();
|
|
}
|
|
}
|
|
},
|
|
},
|
|
);
|
|
|
|
connectionManager.startConnectionLoop();
|
|
|
|
return () => {
|
|
dispatchCallback({
|
|
client,
|
|
status: RelayStatus.disconnected(),
|
|
});
|
|
|
|
(async () => {
|
|
try {
|
|
await connectionManager.cleanup();
|
|
} catch {
|
|
// Ignore disconnect errors during cleanup
|
|
}
|
|
})();
|
|
};
|
|
};
|