@bsv/authsocket-client
Version:
Mutually Authenticated Web Sockets Client
159 lines (158 loc) • 4.54 kB
JavaScript
import { SocketClientTransport } from "./SocketClientTransport.js";
import { io } from "socket.io-client";
import { Peer, Utils, stringifyBRC100 } from "@bsv/sdk";
//#region src/AuthSocketClient.ts
function decodeAuthSocketEventPayload(payload) {
try {
const str = Utils.toUTF8(payload);
const decoded = JSON.parse(str);
if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded) || typeof decoded.eventName !== "string") return {
eventName: "_unknown",
data: void 0
};
return {
eventName: decoded.eventName,
data: decoded.data
};
} catch {
return {
eventName: "_unknown",
data: void 0
};
}
}
/**
* Internal class that wraps a Socket.IO client connection with BRC-103 mutual authentication,
* enabling secure and identity-aware communication with a server.
*/
var AuthSocketClientImpl = class {
ioSocket;
peer;
onError;
connected = false;
id = "";
serverIdentityKey;
eventCallbacks = /* @__PURE__ */ new Map();
/**
* Creates an instance of AuthSocketClient.
*
* @param ioSocket - The underlying Socket.IO client socket instance.
* @param peer - The BRC-103 Peer instance responsible for managing authenticated
* communication, including message signing and verification.
*/
constructor(ioSocket, peer, onError = () => {}) {
this.ioSocket = ioSocket;
this.peer = peer;
this.onError = onError;
this.ioSocket.on("connect", () => {
this.connected = true;
this.id = this.ioSocket.id ?? "";
this.fireEventCallbacks("connect");
});
this.ioSocket.on("disconnect", (reason) => {
this.connected = false;
this.fireEventCallbacks("disconnect", reason);
});
this.peer.listenForGeneralMessages(async (senderKey, payload) => {
this.serverIdentityKey = senderKey;
const { eventName, data } = this.decodeEventPayload(payload);
await this.fireEventCallbacks(eventName, data);
});
}
on(eventName, callback) {
let arr = this.eventCallbacks.get(eventName);
if (arr === void 0) {
arr = [];
this.eventCallbacks.set(eventName, arr);
}
arr.push(callback);
return this;
}
emit(eventName, data) {
let encoded;
try {
encoded = this.encodeEventPayload(eventName, data);
} catch (error) {
this.reportError(error, {
phase: "send",
socketId: this.ioSocket.id ?? this.id,
eventName
});
return this;
}
this.peer.toPeer(encoded, this.serverIdentityKey).catch((err) => {
this.reportError(err, {
phase: "send",
socketId: this.ioSocket.id ?? this.id,
eventName
});
});
return this;
}
disconnect() {
this.serverIdentityKey = void 0;
this.ioSocket.disconnect();
}
async fireEventCallbacks(eventName, data) {
const cbs = this.eventCallbacks.get(eventName);
if (cbs === void 0) return;
try {
for (const cb of cbs) {
const result = cb(data);
if (result != null && typeof result.then === "function") await result;
}
} catch (error) {
this.reportError(error, {
phase: "application",
socketId: this.ioSocket.id ?? this.id,
eventName
});
if (eventName !== "disconnect") this.disconnectSafely();
}
}
encodeEventPayload(eventName, data) {
const obj = {
eventName,
data
};
return Utils.toArray(stringifyBRC100(obj), "utf8");
}
decodeEventPayload(payload) {
return decodeAuthSocketEventPayload(payload);
}
reportError(error, context) {
Promise.resolve().then(async () => await this.onError(error, context)).catch(() => {});
}
disconnectSafely() {
try {
this.ioSocket.disconnect();
} catch {}
}
};
/**
* Factory function for creating a new AuthSocketClientImpl instance.
*
* @param url - The server URL
* @param opts - Contains wallet, requested certificates, and other optional settings
*/
function AuthSocketClient(url, opts) {
const socket = io(url, opts.managerOptions);
const transport = new SocketClientTransport(socket, {
maxPendingMessages: opts.maxPendingAuthMessages,
onError: (error) => {
reportErrorSafely(opts.onError, error, {
phase: "authentication",
socketId: socket.id
});
}
});
return new AuthSocketClientImpl(socket, new Peer(opts.wallet, transport, opts.requestedCertificates, opts.sessionManager, void 0, opts.originator), (error, context) => {
reportErrorSafely(opts.onError, error, context);
});
}
function reportErrorSafely(handler, error, context) {
Promise.resolve().then(async () => await handler?.(error, context)).catch(() => {});
}
//#endregion
export { AuthSocketClient, decodeAuthSocketEventPayload };
//# sourceMappingURL=AuthSocketClient.js.map