@openlv/session 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -5
- package/dist/index.js +178 -111
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +2 -2
- package/dist/index.test.js.map +1 -1
- package/dist/messages/correlate.d.ts +12 -0
- package/dist/messages/correlate.js +45 -0
- package/dist/messages/correlate.js.map +1 -0
- package/dist/options.test.d.ts +1 -0
- package/dist/options.test.js +27 -0
- package/dist/options.test.js.map +1 -0
- package/dist/transports.d.ts +7 -0
- package/dist/transports.js +12 -0
- package/dist/transports.js.map +1 -0
- package/dist/transports.test.d.ts +1 -0
- package/dist/transports.test.js +22 -0
- package/dist/transports.test.js.map +1 -0
- package/dist/utils/log.d.ts +1 -1
- package/dist/utils/log.js +2 -4
- package/dist/utils/log.js.map +1 -1
- package/package.json +6 -12
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type SessionHandshakeParameters, type SessionLinkParameters } from "@openlv/core";
|
|
2
|
-
import { type SignalingLayer, type SignalingProtocol, type SignalState } from "@openlv/signaling";
|
|
3
|
-
import { type
|
|
2
|
+
import { type PeerInfo, type SignalingLayer, type SignalingProtocol, type SignalState } from "@openlv/signaling";
|
|
3
|
+
import { type TransportLayer, type TransportLayerFn } from "@openlv/transport";
|
|
4
4
|
import { EventEmitter } from "eventemitter3";
|
|
5
5
|
import type { SessionEvents } from "./events.js";
|
|
6
|
+
export type { PeerCapabilities, PeerInfo } from "@openlv/signaling";
|
|
6
7
|
export declare const SESSION_STATE: {
|
|
7
8
|
readonly CREATED: "created";
|
|
8
9
|
readonly SIGNALING: "signaling";
|
|
@@ -17,6 +18,14 @@ export type SessionStateObject = {
|
|
|
17
18
|
signaling?: {
|
|
18
19
|
state: SignalState;
|
|
19
20
|
};
|
|
21
|
+
/** The peer's self-description, set once capabilities are exchanged. */
|
|
22
|
+
peerInfo?: PeerInfo;
|
|
23
|
+
/** Human-readable reason for the last disconnect/failure, if any. */
|
|
24
|
+
error?: string;
|
|
25
|
+
};
|
|
26
|
+
export type SessionOptions = {
|
|
27
|
+
/** Shared with the peer during the handshake and shown in its UI. */
|
|
28
|
+
info?: PeerInfo;
|
|
20
29
|
};
|
|
21
30
|
/**
|
|
22
31
|
* an OpenLV Session
|
|
@@ -43,7 +52,8 @@ export type Session = {
|
|
|
43
52
|
emitter: EventEmitter<SessionEvents>;
|
|
44
53
|
_internal: {
|
|
45
54
|
signal: SignalingLayer;
|
|
46
|
-
transport
|
|
55
|
+
/** Instantiated when signaling reaches ENCRYPTED and a transport is selected. */
|
|
56
|
+
transport: TransportLayer | undefined;
|
|
47
57
|
};
|
|
48
58
|
};
|
|
49
59
|
/**
|
|
@@ -51,8 +61,8 @@ export type Session = {
|
|
|
51
61
|
*
|
|
52
62
|
* https://openlv.sh/api/session
|
|
53
63
|
*/
|
|
54
|
-
export declare const createSession: (initParameters: SessionLinkParameters, signalLayer: SignalingProtocol,
|
|
64
|
+
export declare const createSession: (initParameters: SessionLinkParameters, signalLayer: SignalingProtocol, transportLayers: TransportLayerFn[], onMessage: (message: object) => Promise<object | string>, options?: SessionOptions) => Promise<Session>;
|
|
55
65
|
/**
|
|
56
66
|
* Connect to a session from its openlv:// URL
|
|
57
67
|
*/
|
|
58
|
-
export declare const connectSession: (connectionUrl: string, onMessage: (message: object) => Promise<object | string>,
|
|
68
|
+
export declare const connectSession: (connectionUrl: string, onMessage: (message: object) => Promise<object | string>, transports: TransportLayerFn[], options?: SessionOptions) => Promise<Session>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { decodeConnectionURL, OPENLV_PROTOCOL_VERSION, } from "@openlv/core";
|
|
2
2
|
import { deriveSymmetricKey, generateHandshakeKey, generateSessionId, initEncryptionKeys, initHash, parseEncryptionKey, } from "@openlv/core/encryption";
|
|
3
|
-
import { SIGNAL_STATE, } from "@openlv/signaling";
|
|
3
|
+
import { SIGNAL_STATE, validatePeerInfo, } from "@openlv/signaling";
|
|
4
4
|
import { dynamicSignalingLayer } from "@openlv/signaling/dynamic";
|
|
5
5
|
import { TRANSPORT_STATE, } from "@openlv/transport";
|
|
6
6
|
import { EventEmitter } from "eventemitter3";
|
|
7
|
+
import { awaitCorrelatedResponse } from "./messages/correlate.js";
|
|
8
|
+
import { selectTransportId } from "./transports.js";
|
|
7
9
|
import { log } from "./utils/log.js";
|
|
8
10
|
export const SESSION_STATE = {
|
|
9
11
|
CREATED: "created",
|
|
@@ -18,7 +20,17 @@ export const SESSION_STATE = {
|
|
|
18
20
|
*
|
|
19
21
|
* https://openlv.sh/api/session
|
|
20
22
|
*/
|
|
21
|
-
export const createSession = async (initParameters, signalLayer,
|
|
23
|
+
export const createSession = async (initParameters, signalLayer, transportLayers, onMessage, options) => {
|
|
24
|
+
if (transportLayers.length === 0) {
|
|
25
|
+
throw new Error("At least one transport is required");
|
|
26
|
+
}
|
|
27
|
+
if (options?.info) {
|
|
28
|
+
// A receiver silently drops out-of-bounds info with the whole packet,
|
|
29
|
+
// which would surface only as a handshake timeout — fail loudly here.
|
|
30
|
+
const problem = validatePeerInfo(options.info);
|
|
31
|
+
if (problem)
|
|
32
|
+
throw new Error(`Invalid session info: ${problem}`);
|
|
33
|
+
}
|
|
22
34
|
const emitter = new EventEmitter();
|
|
23
35
|
const messages = new EventEmitter();
|
|
24
36
|
const sessionId = "sessionId" in initParameters
|
|
@@ -31,12 +43,23 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
31
43
|
: await generateHandshakeKey();
|
|
32
44
|
const { hash, isHost } = await initHash("h" in initParameters ? initParameters.h : undefined, encryptionKey);
|
|
33
45
|
let status = SESSION_STATE.CREATED;
|
|
46
|
+
let lastError;
|
|
34
47
|
const protocol = initParameters.p;
|
|
35
48
|
const server = initParameters.s;
|
|
49
|
+
const capabilities = {
|
|
50
|
+
transports: transportLayers.map(layer => layer.transportId),
|
|
51
|
+
...(options?.info ? { info: options.info } : {}),
|
|
52
|
+
};
|
|
53
|
+
let peerCaps;
|
|
36
54
|
const updateStatus = (newStatus) => {
|
|
37
55
|
log("updateStatus", newStatus);
|
|
38
56
|
status = newStatus;
|
|
39
|
-
emitter.emit("state_change", {
|
|
57
|
+
emitter.emit("state_change", {
|
|
58
|
+
status,
|
|
59
|
+
signaling: signal.getState(),
|
|
60
|
+
peerInfo: peerCaps?.info,
|
|
61
|
+
error: lastError,
|
|
62
|
+
});
|
|
40
63
|
};
|
|
41
64
|
const signaling = await signalLayer({
|
|
42
65
|
topic: sessionId,
|
|
@@ -51,7 +74,6 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
51
74
|
if (!relyingPublicKey) {
|
|
52
75
|
throw new Error("Relying party public key not found");
|
|
53
76
|
}
|
|
54
|
-
log(`encrypting to ${relyingPublicKey.toString()}`, message);
|
|
55
77
|
return await relyingPublicKey.encrypt(message);
|
|
56
78
|
},
|
|
57
79
|
decrypt,
|
|
@@ -62,9 +84,18 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
62
84
|
log(`rpKey discovered by ${role}`, rpKey);
|
|
63
85
|
relyingPublicKey = await parseEncryptionKey(rpKey);
|
|
64
86
|
},
|
|
87
|
+
capabilities,
|
|
88
|
+
peerCapabilities(received) {
|
|
89
|
+
log("peer capabilities received", received);
|
|
90
|
+
peerCaps = received;
|
|
91
|
+
// Re-emit the current status so observers see peerInfo the moment the
|
|
92
|
+
// capabilities exchange completes, not at the next status transition.
|
|
93
|
+
updateStatus(status);
|
|
94
|
+
},
|
|
65
95
|
isHost,
|
|
66
96
|
});
|
|
67
|
-
|
|
97
|
+
let transport;
|
|
98
|
+
const createTransport = (layer) => layer.create({
|
|
68
99
|
encrypt(message) {
|
|
69
100
|
if (!relyingPublicKey) {
|
|
70
101
|
throw new Error("Relying party public key not found");
|
|
@@ -77,19 +108,26 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
77
108
|
log("Session: received message from transport", message);
|
|
78
109
|
if (message["type"] === "request") {
|
|
79
110
|
const messageId = message["messageId"];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
try {
|
|
112
|
+
// Immediately acknowledge receipt so the sender's ack-timeout does
|
|
113
|
+
// not fire while the handler (which may await user interaction) runs.
|
|
114
|
+
await transport?.send({ type: "ack", messageId });
|
|
115
|
+
// Notify observers before processing (e.g. wallet UI can show a
|
|
116
|
+
// pending indicator before the handler resolves).
|
|
117
|
+
emitter.emit("request", message["payload"]);
|
|
118
|
+
const data = await onMessage(message["payload"])
|
|
119
|
+
// A throwing handler must still answer, otherwise the peer waits
|
|
120
|
+
// out its full response timeout.
|
|
121
|
+
.catch(() => ({ error: { code: -32603, message: "Internal error" } }));
|
|
122
|
+
await transport?.send({
|
|
123
|
+
type: "response",
|
|
124
|
+
messageId,
|
|
125
|
+
payload: data,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
log("failed to respond to peer request", error);
|
|
130
|
+
}
|
|
93
131
|
}
|
|
94
132
|
if (message["type"] === "response" || message["type"] === "ack") {
|
|
95
133
|
// Both acks and responses are forwarded to the send() correlator.
|
|
@@ -106,62 +144,128 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
106
144
|
await signal.send(sessionMessage);
|
|
107
145
|
},
|
|
108
146
|
});
|
|
109
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Sessions resumed with a known peer key never exchange capabilities and
|
|
149
|
+
* keep the first configured transport.
|
|
150
|
+
*/
|
|
151
|
+
const selectTransportLayer = () => {
|
|
152
|
+
if (!peerCaps)
|
|
153
|
+
return transportLayers[0];
|
|
154
|
+
const selected = selectTransportId(isHost, capabilities.transports, peerCaps.transports);
|
|
155
|
+
return transportLayers.find(layer => layer.transportId === selected);
|
|
156
|
+
};
|
|
157
|
+
// Once both peers are present (signaling encrypted) the transport should
|
|
158
|
+
// connect promptly; if it cannot (e.g. no ICE candidates on a restricted
|
|
159
|
+
// network) fail loudly instead of sitting in "linking" forever.
|
|
160
|
+
const TRANSPORT_LINK_TIMEOUT_MS = 45000;
|
|
161
|
+
let linkDeadline;
|
|
162
|
+
const clearLinkDeadline = () => {
|
|
163
|
+
clearTimeout(linkDeadline);
|
|
164
|
+
linkDeadline = undefined;
|
|
165
|
+
};
|
|
166
|
+
const onTransportStateChange = (state) => {
|
|
110
167
|
log("transport state change", state);
|
|
111
168
|
if (state === TRANSPORT_STATE.CONNECTED) {
|
|
169
|
+
clearLinkDeadline();
|
|
170
|
+
lastError = undefined;
|
|
112
171
|
updateStatus(SESSION_STATE.CONNECTED);
|
|
113
172
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
173
|
+
if (state === TRANSPORT_STATE.ERROR) {
|
|
174
|
+
clearLinkDeadline();
|
|
175
|
+
lastError ?? (lastError = "Peer-to-peer transport failed");
|
|
176
|
+
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const onTransportError = (reason) => {
|
|
180
|
+
if (reason)
|
|
181
|
+
lastError = reason;
|
|
182
|
+
};
|
|
183
|
+
const startTransport = () => {
|
|
184
|
+
if (!transport) {
|
|
185
|
+
const layer = selectTransportLayer();
|
|
186
|
+
if (!layer) {
|
|
187
|
+
log("no common transport with peer", capabilities.transports, peerCaps?.transports);
|
|
188
|
+
lastError ?? (lastError = "No common transport with peer");
|
|
189
|
+
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
log("selected transport", layer.transportId);
|
|
193
|
+
transport = createTransport(layer);
|
|
194
|
+
transport.emitter.on("state_change", onTransportStateChange);
|
|
195
|
+
transport.emitter.on("error", onTransportError);
|
|
196
|
+
}
|
|
197
|
+
linkDeadline ?? (linkDeadline = setTimeout(() => {
|
|
198
|
+
if (status === SESSION_STATE.CONNECTED)
|
|
199
|
+
return;
|
|
200
|
+
log("transport failed to connect in time");
|
|
201
|
+
lastError ?? (lastError = "Timed out establishing the peer-to-peer connection");
|
|
202
|
+
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
203
|
+
}, TRANSPORT_LINK_TIMEOUT_MS));
|
|
204
|
+
Promise.resolve(transport.setup()).catch((error) => {
|
|
205
|
+
log("transport setup failed", error);
|
|
206
|
+
clearLinkDeadline();
|
|
207
|
+
lastError ?? (lastError = error instanceof Error ? error.message : "Transport setup failed");
|
|
208
|
+
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
209
|
+
});
|
|
117
210
|
};
|
|
118
|
-
// let transport: TransportLayer | undefined;
|
|
119
211
|
const onSignalMessage = async (message) => {
|
|
120
212
|
log("Session: received message from signaling", message);
|
|
121
213
|
const sessionMsg = message;
|
|
122
214
|
if (sessionMsg.type === "response") {
|
|
123
|
-
log("Session: received data message", sessionMsg.payload);
|
|
124
215
|
messages.emit("message", sessionMsg);
|
|
125
216
|
}
|
|
126
217
|
if (sessionMsg.type === "request") {
|
|
127
|
-
|
|
128
|
-
|
|
218
|
+
if (!transport) {
|
|
219
|
+
log("dropping negotiation message: transport not selected yet");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
await transport.handle(sessionMsg.payload);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
// Negotiation payloads come from the (untrusted) relay; a malformed
|
|
227
|
+
// one must not become an unhandled rejection.
|
|
228
|
+
log("failed to handle negotiation message", error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const onSignalStateChange = (state) => {
|
|
233
|
+
log("signal state change", state);
|
|
234
|
+
if (state === SIGNAL_STATE.READY) {
|
|
235
|
+
updateStatus(SESSION_STATE.READY);
|
|
236
|
+
}
|
|
237
|
+
if ([
|
|
238
|
+
SIGNAL_STATE.HANDSHAKE,
|
|
239
|
+
SIGNAL_STATE.HANDSHAKE_PARTIAL,
|
|
240
|
+
].includes(state)) {
|
|
241
|
+
updateStatus(SESSION_STATE.LINKING);
|
|
242
|
+
}
|
|
243
|
+
if (state === SIGNAL_STATE.ENCRYPTED) {
|
|
244
|
+
startTransport();
|
|
245
|
+
}
|
|
246
|
+
if (state === SIGNAL_STATE.ERROR) {
|
|
247
|
+
log("signaling error — marking session disconnected");
|
|
248
|
+
lastError ?? (lastError = "Signaling failed or timed out");
|
|
249
|
+
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
129
250
|
}
|
|
130
251
|
};
|
|
131
252
|
return {
|
|
132
253
|
connect: async () => {
|
|
133
254
|
updateStatus(SESSION_STATE.SIGNALING);
|
|
134
255
|
log("connecting to session, isHost:", isHost);
|
|
135
|
-
// TODO: implement
|
|
136
|
-
log("connecting to session");
|
|
137
256
|
signal.on("message", onSignalMessage);
|
|
138
|
-
signal.on("state_change",
|
|
139
|
-
log("signal state change", state);
|
|
140
|
-
if (state === SIGNAL_STATE.READY) {
|
|
141
|
-
updateStatus(SESSION_STATE.READY);
|
|
142
|
-
}
|
|
143
|
-
if ([
|
|
144
|
-
SIGNAL_STATE.HANDSHAKE,
|
|
145
|
-
SIGNAL_STATE.HANDSHAKE_PARTIAL,
|
|
146
|
-
].includes(state)) {
|
|
147
|
-
updateStatus(SESSION_STATE.LINKING);
|
|
148
|
-
}
|
|
149
|
-
if (state === SIGNAL_STATE.ENCRYPTED) {
|
|
150
|
-
startTransport();
|
|
151
|
-
}
|
|
152
|
-
if (state === SIGNAL_STATE.ERROR) {
|
|
153
|
-
log("signaling error — marking session disconnected");
|
|
154
|
-
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
// TODO: handle errors in setup nicely in modal UI
|
|
257
|
+
signal.on("state_change", onSignalStateChange);
|
|
158
258
|
await signal.setup();
|
|
159
259
|
},
|
|
160
260
|
async close() {
|
|
161
261
|
log("session teardown");
|
|
262
|
+
clearLinkDeadline();
|
|
162
263
|
signal.off("message", onSignalMessage);
|
|
264
|
+
signal.off("state_change", onSignalStateChange);
|
|
265
|
+
transport?.emitter.off("state_change", onTransportStateChange);
|
|
266
|
+
transport?.emitter.off("error", onTransportError);
|
|
163
267
|
await Promise.all([
|
|
164
|
-
transport
|
|
268
|
+
transport?.teardown(),
|
|
165
269
|
signal.teardown(),
|
|
166
270
|
]);
|
|
167
271
|
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
@@ -170,7 +274,8 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
170
274
|
return {
|
|
171
275
|
status,
|
|
172
276
|
signaling: signal.getState(),
|
|
173
|
-
|
|
277
|
+
peerInfo: peerCaps?.info,
|
|
278
|
+
error: lastError,
|
|
174
279
|
};
|
|
175
280
|
},
|
|
176
281
|
getHandshakeParameters() {
|
|
@@ -183,28 +288,24 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
183
288
|
s: server,
|
|
184
289
|
};
|
|
185
290
|
},
|
|
186
|
-
waitForLink: async () => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
203
|
-
emitter.on("state_change", handler);
|
|
204
|
-
});
|
|
205
|
-
},
|
|
291
|
+
waitForLink: async () => new Promise((resolve, reject) => {
|
|
292
|
+
const handler = (state) => {
|
|
293
|
+
if (state?.status === SESSION_STATE.CONNECTED) {
|
|
294
|
+
emitter.off("state_change", handler);
|
|
295
|
+
resolve();
|
|
296
|
+
}
|
|
297
|
+
else if (state?.status === SESSION_STATE.DISCONNECTED) {
|
|
298
|
+
emitter.off("state_change", handler);
|
|
299
|
+
reject(new Error(lastError ?? "Session failed to connect"));
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
// Subscribe before inspecting the current status so a transition
|
|
303
|
+
// between the check and the subscription cannot be missed.
|
|
304
|
+
emitter.on("state_change", handler);
|
|
305
|
+
handler({ status });
|
|
306
|
+
}),
|
|
206
307
|
async send(message, ackTimeout = 10000, responseTimeout = 60 * 60000) {
|
|
207
|
-
if (signal.getState().state !== SIGNAL_STATE.ENCRYPTED) {
|
|
308
|
+
if (!transport || signal.getState().state !== SIGNAL_STATE.ENCRYPTED) {
|
|
208
309
|
throw new Error("Session not ready");
|
|
209
310
|
}
|
|
210
311
|
const messageId = crypto.randomUUID();
|
|
@@ -214,47 +315,13 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
214
315
|
payload: message,
|
|
215
316
|
};
|
|
216
317
|
await transport.send(sessionMessage);
|
|
217
|
-
return
|
|
218
|
-
let ackReceived = false;
|
|
219
|
-
// eslint-disable-next-line prefer-const
|
|
220
|
-
let ackTimer;
|
|
221
|
-
let responseTimer;
|
|
222
|
-
const cleanup = () => {
|
|
223
|
-
clearTimeout(ackTimer);
|
|
224
|
-
clearTimeout(responseTimer);
|
|
225
|
-
messages.off("message", handler);
|
|
226
|
-
};
|
|
227
|
-
const handler = (msg) => {
|
|
228
|
-
if (msg.messageId !== messageId)
|
|
229
|
-
return;
|
|
230
|
-
if (msg.type === "ack" && !ackReceived) {
|
|
231
|
-
ackReceived = true;
|
|
232
|
-
clearTimeout(ackTimer);
|
|
233
|
-
// The other side confirmed receipt; wait for the full response.
|
|
234
|
-
responseTimer = setTimeout(() => {
|
|
235
|
-
cleanup();
|
|
236
|
-
reject(new Error("Request timed out: no response after acknowledgement"));
|
|
237
|
-
}, responseTimeout);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (msg.type === "response") {
|
|
241
|
-
cleanup();
|
|
242
|
-
resolve(msg.payload);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
messages.on("message", handler);
|
|
246
|
-
// Short window for the ack — tells us the peer is alive and processing.
|
|
247
|
-
ackTimer = setTimeout(() => {
|
|
248
|
-
if (!ackReceived) {
|
|
249
|
-
cleanup();
|
|
250
|
-
reject(new Error("Request timed out: remote peer did not acknowledge"));
|
|
251
|
-
}
|
|
252
|
-
}, ackTimeout);
|
|
253
|
-
});
|
|
318
|
+
return awaitCorrelatedResponse(messages, messageId, ackTimeout, responseTimeout);
|
|
254
319
|
},
|
|
255
320
|
_internal: {
|
|
256
321
|
signal,
|
|
257
|
-
transport
|
|
322
|
+
get transport() {
|
|
323
|
+
return transport;
|
|
324
|
+
},
|
|
258
325
|
},
|
|
259
326
|
emitter,
|
|
260
327
|
};
|
|
@@ -262,12 +329,12 @@ export const createSession = async (initParameters, signalLayer, transportLayer,
|
|
|
262
329
|
/**
|
|
263
330
|
* Connect to a session from its openlv:// URL
|
|
264
331
|
*/
|
|
265
|
-
export const connectSession = async (connectionUrl, onMessage,
|
|
332
|
+
export const connectSession = async (connectionUrl, onMessage, transports, options) => {
|
|
266
333
|
const initParameters = decodeConnectionURL(connectionUrl);
|
|
267
334
|
const signaling = await dynamicSignalingLayer(initParameters.p);
|
|
268
335
|
if (!signaling) {
|
|
269
336
|
throw new Error(`Invalid signaling protocol: ${initParameters.p}`);
|
|
270
337
|
}
|
|
271
|
-
return createSession(initParameters, signaling,
|
|
338
|
+
return createSession(initParameters, signaling, transports, onMessage, options);
|
|
272
339
|
};
|
|
273
340
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n decodeConnectionURL,\n OPENLV_PROTOCOL_VERSION,\n type SessionHandshakeParameters,\n type SessionLinkParameters,\n} from \"@openlv/core\";\nimport {\n deriveSymmetricKey,\n type EncryptionKey,\n generateHandshakeKey,\n generateSessionId,\n initEncryptionKeys,\n initHash,\n parseEncryptionKey,\n} from \"@openlv/core/encryption\";\nimport {\n SIGNAL_STATE,\n type SignalingLayer,\n type SignalingProtocol,\n type SignalState,\n} from \"@openlv/signaling\";\nimport { dynamicSignalingLayer } from \"@openlv/signaling/dynamic\";\nimport {\n type TLayer,\n TRANSPORT_STATE,\n type TransportLayer,\n type TransportMessage,\n} from \"@openlv/transport\";\nimport { EventEmitter } from \"eventemitter3\";\n\nimport type { SessionEvents } from \"./events.js\";\nimport type { SessionMessage } from \"./messages/index.js\";\nimport { log } from \"./utils/log.js\";\n\nexport const SESSION_STATE = {\n CREATED: \"created\",\n SIGNALING: \"signaling\",\n READY: \"ready\",\n LINKING: \"linking\",\n CONNECTED: \"connected\",\n DISCONNECTED: \"disconnected\",\n} as const;\nexport type SessionState = (typeof SESSION_STATE)[keyof typeof SESSION_STATE];\n\nexport type SessionStateObject = {\n status: SessionState;\n signaling?: {\n state: SignalState;\n };\n};\n\n/**\n * an OpenLV Session\n *\n * https://openlv.sh/api/session\n */\nexport type Session = {\n getState(): SessionStateObject;\n getHandshakeParameters(): SessionHandshakeParameters;\n connect(): Promise<void>;\n waitForLink(): Promise<void>;\n close(): Promise<void>;\n /**\n * Send a request to the remote peer and await a correlated response.\n *\n * Two-phase timeout:\n * - `ackTimeout` (default 10 s): the remote peer must send back an ack\n * within this window, confirming it received the message.\n * - After an ack arrives the wait is extended to `responseTimeout`\n * (default 1 hour) — enough for user-interactive flows such as\n * `eth_sendTransaction` or `personal_sign`.\n */\n send(message: object, ackTimeout?: number, responseTimeout?: number): Promise<unknown>;\n emitter: EventEmitter<SessionEvents>;\n _internal: {\n signal: SignalingLayer;\n transport: TransportLayer;\n };\n};\n\n/**\n * OpenLV Session\n *\n * https://openlv.sh/api/session\n */\nexport const createSession = async (\n initParameters: SessionLinkParameters,\n signalLayer: SignalingProtocol,\n transportLayer: TLayer,\n onMessage: (message: object) => Promise<object | string>,\n): Promise<Session> => {\n const emitter = new EventEmitter<SessionEvents>();\n const messages = new EventEmitter<{ message: SessionMessage; }>();\n const sessionId\n = \"sessionId\" in initParameters\n ? initParameters.sessionId\n : generateSessionId();\n const {\n encryptionKey,\n decryptionKey: { decrypt },\n } = await initEncryptionKeys(initParameters);\n let relyingPublicKey: EncryptionKey | undefined;\n const handshakeKey\n = \"k\" in initParameters\n ? await deriveSymmetricKey(initParameters.k)\n : await generateHandshakeKey();\n const { hash, isHost } = await initHash(\n \"h\" in initParameters ? initParameters.h : undefined,\n encryptionKey,\n );\n let status: SessionState = SESSION_STATE.CREATED;\n const protocol = initParameters.p;\n const server = initParameters.s;\n\n const updateStatus = (newStatus: SessionState) => {\n log(\"updateStatus\", newStatus);\n status = newStatus;\n emitter.emit(\"state_change\", { status, signaling: signal.getState() });\n };\n\n const signaling = await signalLayer({\n topic: sessionId,\n url: server,\n });\n const signal = await signaling({\n h: hash,\n canEncrypt() {\n return relyingPublicKey !== undefined;\n },\n async encrypt(message) {\n if (!relyingPublicKey) {\n throw new Error(\"Relying party public key not found\");\n }\n\n log(`encrypting to ${relyingPublicKey.toString()}`, message);\n\n return await relyingPublicKey.encrypt(message);\n },\n decrypt,\n publicKey: encryptionKey,\n k: handshakeKey,\n async rpDiscovered(rpKey) {\n const role = isHost ? \"host\" : \"client\";\n\n log(`rpKey discovered by ${role}`, rpKey);\n\n relyingPublicKey = await parseEncryptionKey(rpKey);\n },\n isHost,\n });\n\n const transport = transportLayer({\n encrypt(message) {\n if (!relyingPublicKey) {\n throw new Error(\"Relying party public key not found\");\n }\n\n return relyingPublicKey?.encrypt(message);\n },\n decrypt,\n isHost,\n onmessage: async (message: { type: string; payload: object; messageId: string; }) => {\n log(\"Session: received message from transport\", message);\n\n if (message[\"type\"] === \"request\") {\n const messageId = message[\"messageId\"] as string;\n\n // Immediately acknowledge receipt so the sender's ack-timeout does\n // not fire while the handler (which may await user interaction) runs.\n await transport.send({ type: \"ack\", messageId } satisfies SessionMessage);\n\n // Notify observers before processing (e.g. wallet UI can show a\n // pending indicator before the handler resolves).\n emitter.emit(\"request\", message[\"payload\"] as object);\n\n const data = await onMessage(message[\"payload\"] as object);\n const response: SessionMessage = {\n type: \"response\",\n messageId,\n payload: data,\n };\n\n await transport.send(response);\n }\n\n if (message[\"type\"] === \"response\" || message[\"type\"] === \"ack\") {\n // Both acks and responses are forwarded to the send() correlator.\n messages.emit(\"message\", message as SessionMessage);\n }\n },\n async subsend(message) {\n log(\"Session: sending trans msg to signal\", message);\n const sessionMessage: SessionMessage = {\n type: \"request\",\n messageId: crypto.randomUUID(),\n payload: message,\n };\n\n await signal.send(sessionMessage);\n },\n });\n\n transport.emitter.on(\"state_change\", (state) => {\n log(\"transport state change\", state);\n\n if (state === TRANSPORT_STATE.CONNECTED) {\n updateStatus(SESSION_STATE.CONNECTED);\n }\n });\n\n const startTransport = async () => {\n await transport.setup();\n };\n\n // let transport: TransportLayer | undefined;\n const onSignalMessage = async (message: object) => {\n log(\"Session: received message from signaling\", message);\n\n const sessionMsg = message as SessionMessage;\n\n if (sessionMsg.type === \"response\") {\n log(\"Session: received data message\", sessionMsg.payload);\n messages.emit(\"message\", sessionMsg);\n }\n\n if (sessionMsg.type === \"request\") {\n log(\"Session: received request message\", sessionMsg.payload);\n\n await transport.handle(sessionMsg.payload as TransportMessage);\n }\n };\n\n return {\n connect: async () => {\n updateStatus(SESSION_STATE.SIGNALING);\n log(\"connecting to session, isHost:\", isHost);\n // TODO: implement\n log(\"connecting to session\");\n\n signal.on(\"message\", onSignalMessage);\n\n signal.on(\"state_change\", (state) => {\n log(\"signal state change\", state);\n\n if (state === SIGNAL_STATE.READY) {\n updateStatus(SESSION_STATE.READY);\n }\n\n if (\n (\n [\n SIGNAL_STATE.HANDSHAKE,\n SIGNAL_STATE.HANDSHAKE_PARTIAL,\n ] as SignalState[]\n ).includes(state)\n ) {\n updateStatus(SESSION_STATE.LINKING);\n }\n\n if (state === SIGNAL_STATE.ENCRYPTED) {\n startTransport();\n }\n\n if (state === SIGNAL_STATE.ERROR) {\n log(\"signaling error — marking session disconnected\");\n updateStatus(SESSION_STATE.DISCONNECTED);\n }\n });\n\n // TODO: handle errors in setup nicely in modal UI\n await signal.setup();\n },\n async close() {\n log(\"session teardown\");\n signal.off(\"message\", onSignalMessage);\n await Promise.all([\n transport.teardown(),\n signal.teardown(),\n ]);\n updateStatus(SESSION_STATE.DISCONNECTED);\n },\n getState() {\n return {\n status,\n signaling: signal.getState(),\n // transport: transport.getState(),\n };\n },\n getHandshakeParameters() {\n return {\n version: OPENLV_PROTOCOL_VERSION,\n sessionId,\n h: hash,\n k: handshakeKey.toString(),\n p: protocol,\n s: server,\n };\n },\n waitForLink: async () => {\n if (status === SESSION_STATE.CONNECTED) return;\n\n if (status === SESSION_STATE.DISCONNECTED) {\n throw new Error(\"Session failed to connect\");\n }\n\n return new Promise<void>((resolve, reject) => {\n const handler = (state?: SessionStateObject) => {\n if (state?.status === SESSION_STATE.CONNECTED) {\n emitter.off(\"state_change\", handler);\n resolve();\n }\n else if (state?.status === SESSION_STATE.DISCONNECTED) {\n emitter.off(\"state_change\", handler);\n reject(new Error(\"Session failed to connect\"));\n }\n };\n\n emitter.on(\"state_change\", handler);\n });\n },\n async send(\n message: object,\n ackTimeout: number = 10_000,\n responseTimeout: number = 60 * 60_000,\n ) {\n if (signal.getState().state !== SIGNAL_STATE.ENCRYPTED) {\n throw new Error(\"Session not ready\");\n }\n\n const messageId = crypto.randomUUID();\n const sessionMessage: SessionMessage = {\n type: \"request\",\n messageId,\n payload: message,\n };\n\n await transport.send(sessionMessage);\n\n return new Promise<unknown>((resolve, reject) => {\n let ackReceived = false;\n // eslint-disable-next-line prefer-const\n let ackTimer: ReturnType<typeof setTimeout> | undefined;\n let responseTimer: ReturnType<typeof setTimeout> | undefined;\n\n const cleanup = () => {\n clearTimeout(ackTimer);\n clearTimeout(responseTimer);\n messages.off(\"message\", handler);\n };\n\n const handler = (msg: SessionMessage) => {\n if (msg.messageId !== messageId) return;\n\n if (msg.type === \"ack\" && !ackReceived) {\n ackReceived = true;\n clearTimeout(ackTimer);\n // The other side confirmed receipt; wait for the full response.\n responseTimer = setTimeout(() => {\n cleanup();\n reject(new Error(\"Request timed out: no response after acknowledgement\"));\n }, responseTimeout);\n\n return;\n }\n\n if (msg.type === \"response\") {\n cleanup();\n resolve(msg.payload);\n }\n };\n\n messages.on(\"message\", handler);\n\n // Short window for the ack — tells us the peer is alive and processing.\n ackTimer = setTimeout(() => {\n if (!ackReceived) {\n cleanup();\n reject(new Error(\"Request timed out: remote peer did not acknowledge\"));\n }\n }, ackTimeout);\n });\n },\n _internal: {\n signal,\n transport,\n },\n emitter,\n };\n};\n\n/**\n * Connect to a session from its openlv:// URL\n */\nexport const connectSession = async (\n connectionUrl: string,\n onMessage: (message: object) => Promise<object | string>,\n transport: TLayer,\n): Promise<Session> => {\n const initParameters = decodeConnectionURL(connectionUrl);\n\n const signaling = await dynamicSignalingLayer(initParameters.p);\n\n if (!signaling) {\n throw new Error(`Invalid signaling protocol: ${initParameters.p}`);\n }\n\n return createSession(initParameters, signaling, transport, onMessage);\n};\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n decodeConnectionURL,\n OPENLV_PROTOCOL_VERSION,\n type SessionHandshakeParameters,\n type SessionLinkParameters,\n} from \"@openlv/core\";\nimport {\n deriveSymmetricKey,\n type EncryptionKey,\n generateHandshakeKey,\n generateSessionId,\n initEncryptionKeys,\n initHash,\n parseEncryptionKey,\n} from \"@openlv/core/encryption\";\nimport {\n type PeerCapabilities,\n type PeerInfo,\n SIGNAL_STATE,\n type SignalingLayer,\n type SignalingProtocol,\n type SignalState,\n validatePeerInfo,\n} from \"@openlv/signaling\";\nimport { dynamicSignalingLayer } from \"@openlv/signaling/dynamic\";\nimport {\n TRANSPORT_STATE,\n type TransportLayer,\n type TransportLayerFn,\n type TransportMessage,\n} from \"@openlv/transport\";\nimport { EventEmitter } from \"eventemitter3\";\n\nimport type { SessionEvents } from \"./events.js\";\nimport { awaitCorrelatedResponse } from \"./messages/correlate.js\";\nimport type { SessionMessage } from \"./messages/index.js\";\nimport { selectTransportId } from \"./transports.js\";\nimport { log } from \"./utils/log.js\";\n\nexport type { PeerCapabilities, PeerInfo } from \"@openlv/signaling\";\n\nexport const SESSION_STATE = {\n CREATED: \"created\",\n SIGNALING: \"signaling\",\n READY: \"ready\",\n LINKING: \"linking\",\n CONNECTED: \"connected\",\n DISCONNECTED: \"disconnected\",\n} as const;\nexport type SessionState = (typeof SESSION_STATE)[keyof typeof SESSION_STATE];\n\nexport type SessionStateObject = {\n status: SessionState;\n signaling?: {\n state: SignalState;\n };\n /** The peer's self-description, set once capabilities are exchanged. */\n peerInfo?: PeerInfo;\n /** Human-readable reason for the last disconnect/failure, if any. */\n error?: string;\n};\n\nexport type SessionOptions = {\n /** Shared with the peer during the handshake and shown in its UI. */\n info?: PeerInfo;\n};\n\n/**\n * an OpenLV Session\n *\n * https://openlv.sh/api/session\n */\nexport type Session = {\n getState(): SessionStateObject;\n getHandshakeParameters(): SessionHandshakeParameters;\n connect(): Promise<void>;\n waitForLink(): Promise<void>;\n close(): Promise<void>;\n /**\n * Send a request to the remote peer and await a correlated response.\n *\n * Two-phase timeout:\n * - `ackTimeout` (default 10 s): the remote peer must send back an ack\n * within this window, confirming it received the message.\n * - After an ack arrives the wait is extended to `responseTimeout`\n * (default 1 hour) — enough for user-interactive flows such as\n * `eth_sendTransaction` or `personal_sign`.\n */\n send(message: object, ackTimeout?: number, responseTimeout?: number): Promise<unknown>;\n emitter: EventEmitter<SessionEvents>;\n _internal: {\n signal: SignalingLayer;\n /** Instantiated when signaling reaches ENCRYPTED and a transport is selected. */\n transport: TransportLayer | undefined;\n };\n};\n\n/**\n * OpenLV Session\n *\n * https://openlv.sh/api/session\n */\nexport const createSession = async (\n initParameters: SessionLinkParameters,\n signalLayer: SignalingProtocol,\n transportLayers: TransportLayerFn[],\n onMessage: (message: object) => Promise<object | string>,\n options?: SessionOptions,\n): Promise<Session> => {\n if (transportLayers.length === 0) {\n throw new Error(\"At least one transport is required\");\n }\n\n if (options?.info) {\n // A receiver silently drops out-of-bounds info with the whole packet,\n // which would surface only as a handshake timeout — fail loudly here.\n const problem = validatePeerInfo(options.info);\n\n if (problem) throw new Error(`Invalid session info: ${problem}`);\n }\n\n const emitter = new EventEmitter<SessionEvents>();\n const messages = new EventEmitter<{ message: SessionMessage; }>();\n const sessionId\n = \"sessionId\" in initParameters\n ? initParameters.sessionId\n : generateSessionId();\n const {\n encryptionKey,\n decryptionKey: { decrypt },\n } = await initEncryptionKeys(initParameters);\n let relyingPublicKey: EncryptionKey | undefined;\n const handshakeKey\n = \"k\" in initParameters\n ? await deriveSymmetricKey(initParameters.k)\n : await generateHandshakeKey();\n const { hash, isHost } = await initHash(\n \"h\" in initParameters ? initParameters.h : undefined,\n encryptionKey,\n );\n let status: SessionState = SESSION_STATE.CREATED;\n let lastError: string | undefined;\n const protocol = initParameters.p;\n const server = initParameters.s;\n const capabilities: PeerCapabilities = {\n transports: transportLayers.map(layer => layer.transportId),\n ...(options?.info ? { info: options.info } : {}),\n };\n let peerCaps: PeerCapabilities | undefined;\n\n const updateStatus = (newStatus: SessionState) => {\n log(\"updateStatus\", newStatus);\n status = newStatus;\n emitter.emit(\"state_change\", {\n status,\n signaling: signal.getState(),\n peerInfo: peerCaps?.info,\n error: lastError,\n });\n };\n\n const signaling = await signalLayer({\n topic: sessionId,\n url: server,\n });\n const signal = await signaling({\n h: hash,\n canEncrypt() {\n return relyingPublicKey !== undefined;\n },\n async encrypt(message) {\n if (!relyingPublicKey) {\n throw new Error(\"Relying party public key not found\");\n }\n\n return await relyingPublicKey.encrypt(message);\n },\n decrypt,\n publicKey: encryptionKey,\n k: handshakeKey,\n async rpDiscovered(rpKey) {\n const role = isHost ? \"host\" : \"client\";\n\n log(`rpKey discovered by ${role}`, rpKey);\n\n relyingPublicKey = await parseEncryptionKey(rpKey);\n },\n capabilities,\n peerCapabilities(received) {\n log(\"peer capabilities received\", received);\n peerCaps = received;\n // Re-emit the current status so observers see peerInfo the moment the\n // capabilities exchange completes, not at the next status transition.\n updateStatus(status);\n },\n isHost,\n });\n\n let transport: TransportLayer | undefined;\n\n const createTransport = (layer: TransportLayerFn): TransportLayer => layer.create({\n encrypt(message) {\n if (!relyingPublicKey) {\n throw new Error(\"Relying party public key not found\");\n }\n\n return relyingPublicKey?.encrypt(message);\n },\n decrypt,\n isHost,\n onmessage: async (message: { type: string; payload: object; messageId: string; }) => {\n log(\"Session: received message from transport\", message);\n\n if (message[\"type\"] === \"request\") {\n const messageId = message[\"messageId\"] as string;\n\n try {\n // Immediately acknowledge receipt so the sender's ack-timeout does\n // not fire while the handler (which may await user interaction) runs.\n await transport?.send({ type: \"ack\", messageId } satisfies SessionMessage);\n\n // Notify observers before processing (e.g. wallet UI can show a\n // pending indicator before the handler resolves).\n emitter.emit(\"request\", message[\"payload\"] as object);\n\n const data = await onMessage(message[\"payload\"] as object)\n // A throwing handler must still answer, otherwise the peer waits\n // out its full response timeout.\n .catch(() => ({ error: { code: -32_603, message: \"Internal error\" } }));\n\n await transport?.send({\n type: \"response\",\n messageId,\n payload: data,\n } satisfies SessionMessage);\n }\n catch (error) {\n log(\"failed to respond to peer request\", error);\n }\n }\n\n if (message[\"type\"] === \"response\" || message[\"type\"] === \"ack\") {\n // Both acks and responses are forwarded to the send() correlator.\n messages.emit(\"message\", message as SessionMessage);\n }\n },\n async subsend(message) {\n log(\"Session: sending trans msg to signal\", message);\n const sessionMessage: SessionMessage = {\n type: \"request\",\n messageId: crypto.randomUUID(),\n payload: message,\n };\n\n await signal.send(sessionMessage);\n },\n });\n\n /**\n * Sessions resumed with a known peer key never exchange capabilities and\n * keep the first configured transport.\n */\n const selectTransportLayer = (): TransportLayerFn | undefined => {\n if (!peerCaps) return transportLayers[0];\n\n const selected = selectTransportId(isHost, capabilities.transports, peerCaps.transports);\n\n return transportLayers.find(layer => layer.transportId === selected);\n };\n\n // Once both peers are present (signaling encrypted) the transport should\n // connect promptly; if it cannot (e.g. no ICE candidates on a restricted\n // network) fail loudly instead of sitting in \"linking\" forever.\n const TRANSPORT_LINK_TIMEOUT_MS = 45_000;\n let linkDeadline: ReturnType<typeof setTimeout> | undefined;\n\n const clearLinkDeadline = () => {\n clearTimeout(linkDeadline);\n linkDeadline = undefined;\n };\n\n const onTransportStateChange = (state: string) => {\n log(\"transport state change\", state);\n\n if (state === TRANSPORT_STATE.CONNECTED) {\n clearLinkDeadline();\n lastError = undefined;\n updateStatus(SESSION_STATE.CONNECTED);\n }\n\n if (state === TRANSPORT_STATE.ERROR) {\n clearLinkDeadline();\n lastError ??= \"Peer-to-peer transport failed\";\n updateStatus(SESSION_STATE.DISCONNECTED);\n }\n };\n\n const onTransportError = (reason?: string) => {\n if (reason) lastError = reason;\n };\n\n const startTransport = () => {\n if (!transport) {\n const layer = selectTransportLayer();\n\n if (!layer) {\n log(\"no common transport with peer\", capabilities.transports, peerCaps?.transports);\n lastError ??= \"No common transport with peer\";\n updateStatus(SESSION_STATE.DISCONNECTED);\n\n return;\n }\n\n log(\"selected transport\", layer.transportId);\n transport = createTransport(layer);\n transport.emitter.on(\"state_change\", onTransportStateChange);\n transport.emitter.on(\"error\", onTransportError);\n }\n\n linkDeadline ??= setTimeout(() => {\n if (status === SESSION_STATE.CONNECTED) return;\n\n log(\"transport failed to connect in time\");\n lastError ??= \"Timed out establishing the peer-to-peer connection\";\n updateStatus(SESSION_STATE.DISCONNECTED);\n }, TRANSPORT_LINK_TIMEOUT_MS);\n\n Promise.resolve(transport.setup()).catch((error) => {\n log(\"transport setup failed\", error);\n clearLinkDeadline();\n lastError ??= error instanceof Error ? error.message : \"Transport setup failed\";\n updateStatus(SESSION_STATE.DISCONNECTED);\n });\n };\n\n const onSignalMessage = async (message: object) => {\n log(\"Session: received message from signaling\", message);\n\n const sessionMsg = message as SessionMessage;\n\n if (sessionMsg.type === \"response\") {\n messages.emit(\"message\", sessionMsg);\n }\n\n if (sessionMsg.type === \"request\") {\n if (!transport) {\n log(\"dropping negotiation message: transport not selected yet\");\n\n return;\n }\n\n try {\n await transport.handle(sessionMsg.payload as TransportMessage);\n }\n catch (error) {\n // Negotiation payloads come from the (untrusted) relay; a malformed\n // one must not become an unhandled rejection.\n log(\"failed to handle negotiation message\", error);\n }\n }\n };\n\n const onSignalStateChange = (state: SignalState) => {\n log(\"signal state change\", state);\n\n if (state === SIGNAL_STATE.READY) {\n updateStatus(SESSION_STATE.READY);\n }\n\n if (\n (\n [\n SIGNAL_STATE.HANDSHAKE,\n SIGNAL_STATE.HANDSHAKE_PARTIAL,\n ] as SignalState[]\n ).includes(state)\n ) {\n updateStatus(SESSION_STATE.LINKING);\n }\n\n if (state === SIGNAL_STATE.ENCRYPTED) {\n startTransport();\n }\n\n if (state === SIGNAL_STATE.ERROR) {\n log(\"signaling error — marking session disconnected\");\n lastError ??= \"Signaling failed or timed out\";\n updateStatus(SESSION_STATE.DISCONNECTED);\n }\n };\n\n return {\n connect: async () => {\n updateStatus(SESSION_STATE.SIGNALING);\n log(\"connecting to session, isHost:\", isHost);\n\n signal.on(\"message\", onSignalMessage);\n signal.on(\"state_change\", onSignalStateChange);\n\n await signal.setup();\n },\n async close() {\n log(\"session teardown\");\n clearLinkDeadline();\n signal.off(\"message\", onSignalMessage);\n signal.off(\"state_change\", onSignalStateChange);\n transport?.emitter.off(\"state_change\", onTransportStateChange);\n transport?.emitter.off(\"error\", onTransportError);\n await Promise.all([\n transport?.teardown(),\n signal.teardown(),\n ]);\n updateStatus(SESSION_STATE.DISCONNECTED);\n },\n getState() {\n return {\n status,\n signaling: signal.getState(),\n peerInfo: peerCaps?.info,\n error: lastError,\n };\n },\n getHandshakeParameters() {\n return {\n version: OPENLV_PROTOCOL_VERSION,\n sessionId,\n h: hash,\n k: handshakeKey.toString(),\n p: protocol,\n s: server,\n };\n },\n waitForLink: async () => new Promise<void>((resolve, reject) => {\n const handler = (state?: SessionStateObject) => {\n if (state?.status === SESSION_STATE.CONNECTED) {\n emitter.off(\"state_change\", handler);\n resolve();\n }\n else if (state?.status === SESSION_STATE.DISCONNECTED) {\n emitter.off(\"state_change\", handler);\n reject(new Error(lastError ?? \"Session failed to connect\"));\n }\n };\n\n // Subscribe before inspecting the current status so a transition\n // between the check and the subscription cannot be missed.\n emitter.on(\"state_change\", handler);\n handler({ status });\n }),\n async send(\n message: object,\n ackTimeout: number = 10_000,\n responseTimeout: number = 60 * 60_000,\n ) {\n if (!transport || signal.getState().state !== SIGNAL_STATE.ENCRYPTED) {\n throw new Error(\"Session not ready\");\n }\n\n const messageId = crypto.randomUUID();\n const sessionMessage: SessionMessage = {\n type: \"request\",\n messageId,\n payload: message,\n };\n\n await transport.send(sessionMessage);\n\n return awaitCorrelatedResponse(messages, messageId, ackTimeout, responseTimeout);\n },\n _internal: {\n signal,\n get transport() {\n return transport;\n },\n },\n emitter,\n };\n};\n\n/**\n * Connect to a session from its openlv:// URL\n */\nexport const connectSession = async (\n connectionUrl: string,\n onMessage: (message: object) => Promise<object | string>,\n transports: TransportLayerFn[],\n options?: SessionOptions,\n): Promise<Session> => {\n const initParameters = decodeConnectionURL(connectionUrl);\n\n const signaling = await dynamicSignalingLayer(initParameters.p);\n\n if (!signaling) {\n throw new Error(`Invalid signaling protocol: ${initParameters.p}`);\n }\n\n return createSession(initParameters, signaling, transports, onMessage, options);\n};\n"]}
|
package/dist/index.test.js
CHANGED
|
@@ -9,7 +9,7 @@ describe("Session", () => {
|
|
|
9
9
|
sessionId: "mytestsession111",
|
|
10
10
|
p: "ntfy",
|
|
11
11
|
s: "https://ntfy.sh/",
|
|
12
|
-
}, ntfy, webrtc(), async (message) => {
|
|
12
|
+
}, ntfy, [webrtc()], async (message) => {
|
|
13
13
|
console.log("sessionA received message", message);
|
|
14
14
|
return { result: "success" };
|
|
15
15
|
});
|
|
@@ -27,7 +27,7 @@ describe("Session", () => {
|
|
|
27
27
|
const sessionB = await connectSession(encodedUrl, async (message) => {
|
|
28
28
|
console.log("sessionB received message", message);
|
|
29
29
|
return { result: "success" };
|
|
30
|
-
}, webrtc());
|
|
30
|
+
}, [webrtc()]);
|
|
31
31
|
console.log(sessionB.getState());
|
|
32
32
|
await sessionB.connect();
|
|
33
33
|
await Promise.all([sessionA.waitForLink(), sessionB.waitForLink()]);
|
package/dist/index.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,QAAQ,GAAG,MAAM,aAAa,CAClC;YACE,SAAS,EAAE,kBAAkB;YAC7B,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,kBAAkB;SACtB,EACD,IAAI,EACJ,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,QAAQ,GAAG,MAAM,aAAa,CAClC;YACE,SAAS,EAAE,kBAAkB;YAC7B,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,kBAAkB;SACtB,EACD,IAAI,EACJ,CAAC,MAAM,EAAE,CAAC,EACV,KAAK,EAAE,OAAO,EAAE,EAAE;YAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;YAElD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAC/B,CAAC,CACF,CAAC;QAEF,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEjC,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;QAEzB,MAAM,oBAAoB,GAAG,QAAQ,CAAC,sBAAsB,EAAE,CAAC;QAC/D,MAAM,UAAU,GAAG,mBAAmB,CAAC,oBAAoB,CAAC,CAAC;QAE7D,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAExB,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAEnD,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAEjD,EAAE;QACF,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAExD,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAEvC,MAAM,QAAQ,GAAG,MAAM,cAAc,CACnC,UAAU,EACV,KAAK,EAAE,OAAO,EAAE,EAAE;YAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;YAElD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAC/B,CAAC,EACD,CAAC,MAAM,EAAE,CAAC,CACX,CAAC;QAEF,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjC,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;QAEzB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAEpE,EAAE;QACF,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEtC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAEvD,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAElC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { decodeConnectionURL, encodeConnectionURL } from \"@openlv/core\";\nimport { ntfy } from \"@openlv/signaling/ntfy\";\nimport { webrtc } from \"@openlv/transport/webrtc\";\nimport { describe, expect, test } from \"vitest\";\n\nimport { connectSession, createSession } from \"./index.js\";\n\ndescribe(\"Session\", () => {\n test(\"Should be able to create a session\", async () => {\n const sessionA = await createSession(\n {\n sessionId: \"mytestsession111\",\n p: \"ntfy\",\n s: \"https://ntfy.sh/\",\n },\n ntfy,\n [webrtc()],\n async (message) => {\n console.log(\"sessionA received message\", message);\n\n return { result: \"success\" };\n },\n );\n\n expect(sessionA).toBeDefined();\n console.log(sessionA.getState());\n\n await sessionA.connect();\n\n const handshakeParametersA = sessionA.getHandshakeParameters();\n const encodedUrl = encodeConnectionURL(handshakeParametersA);\n\n console.log(encodedUrl);\n\n const decodedUrl = decodeConnectionURL(encodedUrl);\n\n expect(decodedUrl).toEqual(handshakeParametersA);\n\n //\n await new Promise(resolve => setTimeout(resolve, 1000));\n\n console.log(\"Connecting to session B\");\n\n const sessionB = await connectSession(\n encodedUrl,\n async (message) => {\n console.log(\"sessionB received message\", message);\n\n return { result: \"success\" };\n },\n [webrtc()],\n );\n\n console.log(sessionB.getState());\n await sessionB.connect();\n\n await Promise.all([sessionA.waitForLink(), sessionB.waitForLink()]);\n\n //\n console.log(\"A\", sessionA.getState());\n console.log(\"B\", sessionB.getState());\n\n const response = await sessionA.send({ data: \"test\" });\n\n console.log(\"response\", response);\n\n expect(response).toEqual({ result: \"success\" });\n });\n});\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EventEmitter } from "eventemitter3";
|
|
2
|
+
import type { SessionMessage } from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Await the ack and response correlated to a sent request.
|
|
5
|
+
*
|
|
6
|
+
* Two-phase timeout: the peer must ack within `ackTimeoutMs`, confirming it
|
|
7
|
+
* received the message; after the ack the wait extends to `responseTimeoutMs`
|
|
8
|
+
* — enough for user-interactive flows such as `eth_sendTransaction`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const awaitCorrelatedResponse: (messages: EventEmitter<{
|
|
11
|
+
message: SessionMessage;
|
|
12
|
+
}>, messageId: string, ackTimeoutMs: number, responseTimeoutMs: number) => Promise<unknown>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Await the ack and response correlated to a sent request.
|
|
3
|
+
*
|
|
4
|
+
* Two-phase timeout: the peer must ack within `ackTimeoutMs`, confirming it
|
|
5
|
+
* received the message; after the ack the wait extends to `responseTimeoutMs`
|
|
6
|
+
* — enough for user-interactive flows such as `eth_sendTransaction`.
|
|
7
|
+
*/
|
|
8
|
+
export const awaitCorrelatedResponse = (messages, messageId, ackTimeoutMs, responseTimeoutMs) => new Promise((resolve, reject) => {
|
|
9
|
+
let ackReceived = false;
|
|
10
|
+
// eslint-disable-next-line prefer-const
|
|
11
|
+
let ackTimer;
|
|
12
|
+
let responseTimer;
|
|
13
|
+
const cleanup = () => {
|
|
14
|
+
clearTimeout(ackTimer);
|
|
15
|
+
clearTimeout(responseTimer);
|
|
16
|
+
messages.off("message", handler);
|
|
17
|
+
};
|
|
18
|
+
const handler = (msg) => {
|
|
19
|
+
if (msg.messageId !== messageId)
|
|
20
|
+
return;
|
|
21
|
+
if (msg.type === "ack" && !ackReceived) {
|
|
22
|
+
ackReceived = true;
|
|
23
|
+
clearTimeout(ackTimer);
|
|
24
|
+
// The other side confirmed receipt; wait for the full response.
|
|
25
|
+
responseTimer = setTimeout(() => {
|
|
26
|
+
cleanup();
|
|
27
|
+
reject(new Error("Request timed out: no response after acknowledgement"));
|
|
28
|
+
}, responseTimeoutMs);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (msg.type === "response") {
|
|
32
|
+
cleanup();
|
|
33
|
+
resolve(msg.payload);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
messages.on("message", handler);
|
|
37
|
+
// Short window for the ack — tells us the peer is alive and processing.
|
|
38
|
+
ackTimer = setTimeout(() => {
|
|
39
|
+
if (!ackReceived) {
|
|
40
|
+
cleanup();
|
|
41
|
+
reject(new Error("Request timed out: remote peer did not acknowledge"));
|
|
42
|
+
}
|
|
43
|
+
}, ackTimeoutMs);
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=correlate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlate.js","sourceRoot":"","sources":["../../src/messages/correlate.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,QAAoD,EACpD,SAAiB,EACjB,YAAoB,EACpB,iBAAyB,EACP,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IACrD,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,wCAAwC;IACxC,IAAI,QAAmD,CAAC;IACxD,IAAI,aAAwD,CAAC;IAE7D,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvB,YAAY,CAAC,aAAa,CAAC,CAAC;QAC5B,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAmB,EAAE,EAAE;QACtC,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO;QAExC,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;YACvC,WAAW,GAAG,IAAI,CAAC;YACnB,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvB,gEAAgE;YAChE,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC,CAAC;YAC5E,CAAC,EAAE,iBAAiB,CAAC,CAAC;YAEtB,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5B,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAEhC,wEAAwE;IACxE,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE;QACzB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,EAAE,YAAY,CAAC,CAAC;AACnB,CAAC,CAAC,CAAC","sourcesContent":["import type { EventEmitter } from \"eventemitter3\";\n\nimport type { SessionMessage } from \"./index.js\";\n\n/**\n * Await the ack and response correlated to a sent request.\n *\n * Two-phase timeout: the peer must ack within `ackTimeoutMs`, confirming it\n * received the message; after the ack the wait extends to `responseTimeoutMs`\n * — enough for user-interactive flows such as `eth_sendTransaction`.\n */\nexport const awaitCorrelatedResponse = (\n messages: EventEmitter<{ message: SessionMessage; }>,\n messageId: string,\n ackTimeoutMs: number,\n responseTimeoutMs: number,\n): Promise<unknown> => new Promise((resolve, reject) => {\n let ackReceived = false;\n // eslint-disable-next-line prefer-const\n let ackTimer: ReturnType<typeof setTimeout> | undefined;\n let responseTimer: ReturnType<typeof setTimeout> | undefined;\n\n const cleanup = () => {\n clearTimeout(ackTimer);\n clearTimeout(responseTimer);\n messages.off(\"message\", handler);\n };\n\n const handler = (msg: SessionMessage) => {\n if (msg.messageId !== messageId) return;\n\n if (msg.type === \"ack\" && !ackReceived) {\n ackReceived = true;\n clearTimeout(ackTimer);\n // The other side confirmed receipt; wait for the full response.\n responseTimer = setTimeout(() => {\n cleanup();\n reject(new Error(\"Request timed out: no response after acknowledgement\"));\n }, responseTimeoutMs);\n\n return;\n }\n\n if (msg.type === \"response\") {\n cleanup();\n resolve(msg.payload);\n }\n };\n\n messages.on(\"message\", handler);\n\n // Short window for the ack — tells us the peer is alive and processing.\n ackTimer = setTimeout(() => {\n if (!ackReceived) {\n cleanup();\n reject(new Error(\"Request timed out: remote peer did not acknowledge\"));\n }\n }, ackTimeoutMs);\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { webrtc } from "@openlv/transport/webrtc";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createSession } from "./index.js";
|
|
4
|
+
// The guards under test throw before any signaling connection is attempted.
|
|
5
|
+
const unreachableSignaling = () => {
|
|
6
|
+
throw new Error("signaling should not be reached");
|
|
7
|
+
};
|
|
8
|
+
const linkParameters = { p: "ntfy", s: "https://ntfy.sh/" };
|
|
9
|
+
const onMessage = async () => ({});
|
|
10
|
+
describe("createSession input validation", () => {
|
|
11
|
+
it("rejects an empty transport list", async () => {
|
|
12
|
+
await expect(createSession(linkParameters, unreachableSignaling, [], onMessage)).rejects.toThrow("At least one transport is required");
|
|
13
|
+
});
|
|
14
|
+
it("rejects info a receiver would silently drop", async () => {
|
|
15
|
+
await expect(createSession(linkParameters, unreachableSignaling, [webrtc()], onMessage, {
|
|
16
|
+
info: {
|
|
17
|
+
identity: "com.example.dapp",
|
|
18
|
+
name: "Example dApp",
|
|
19
|
+
icon: "x".repeat(10000),
|
|
20
|
+
},
|
|
21
|
+
})).rejects.toThrow(/icon/);
|
|
22
|
+
await expect(createSession(linkParameters, unreachableSignaling, [webrtc()], onMessage, {
|
|
23
|
+
info: { identity: "", name: "Example dApp" },
|
|
24
|
+
})).rejects.toThrow(/identity/);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=options.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.test.js","sourceRoot":"","sources":["../src/options.test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,4EAA4E;AAC5E,MAAM,oBAAoB,GAAsB,GAAG,EAAE;IACnD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC;AAC5D,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAEnC,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,MAAM,CACV,aAAa,CAAC,cAAc,EAAE,oBAAoB,EAAE,EAAE,EAAE,SAAS,CAAC,CACnE,CAAC,OAAO,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,CACV,aAAa,CAAC,cAAc,EAAE,oBAAoB,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE;YACzE,IAAI,EAAE;gBACJ,QAAQ,EAAE,kBAAkB;gBAC5B,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,KAAM,CAAC;aACzB;SACF,CAAC,CACH,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAE1B,MAAM,MAAM,CACV,aAAa,CAAC,cAAc,EAAE,oBAAoB,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE;YACzE,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;SAC7C,CAAC,CACH,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import type { SignalingProtocol } from \"@openlv/signaling\";\nimport { webrtc } from \"@openlv/transport/webrtc\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { createSession } from \"./index.js\";\n\n// The guards under test throw before any signaling connection is attempted.\nconst unreachableSignaling: SignalingProtocol = () => {\n throw new Error(\"signaling should not be reached\");\n};\n\nconst linkParameters = { p: \"ntfy\", s: \"https://ntfy.sh/\" };\nconst onMessage = async () => ({});\n\ndescribe(\"createSession input validation\", () => {\n it(\"rejects an empty transport list\", async () => {\n await expect(\n createSession(linkParameters, unreachableSignaling, [], onMessage),\n ).rejects.toThrow(\"At least one transport is required\");\n });\n\n it(\"rejects info a receiver would silently drop\", async () => {\n await expect(\n createSession(linkParameters, unreachableSignaling, [webrtc()], onMessage, {\n info: {\n identity: \"com.example.dapp\",\n name: \"Example dApp\",\n icon: \"x\".repeat(10_000),\n },\n }),\n ).rejects.toThrow(/icon/);\n\n await expect(\n createSession(linkParameters, unreachableSignaling, [webrtc()], onMessage, {\n info: { identity: \"\", name: \"Example dApp\" },\n }),\n ).rejects.toThrow(/identity/);\n });\n});\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the transport once both capability lists are known: the first entry
|
|
3
|
+
* in the host's preference list that the client also supports. Both peers
|
|
4
|
+
* compute the same result independently — no confirmation round trip.
|
|
5
|
+
* Unknown identifiers are skipped, not fatal.
|
|
6
|
+
*/
|
|
7
|
+
export declare const selectTransportId: (isHost: boolean, ownTransports: string[], peerTransports: string[]) => string | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the transport once both capability lists are known: the first entry
|
|
3
|
+
* in the host's preference list that the client also supports. Both peers
|
|
4
|
+
* compute the same result independently — no confirmation round trip.
|
|
5
|
+
* Unknown identifiers are skipped, not fatal.
|
|
6
|
+
*/
|
|
7
|
+
export const selectTransportId = (isHost, ownTransports, peerTransports) => {
|
|
8
|
+
const hostPreference = isHost ? ownTransports : peerTransports;
|
|
9
|
+
const clientSupported = new Set(isHost ? peerTransports : ownTransports);
|
|
10
|
+
return hostPreference.find(transportId => clientSupported.has(transportId));
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=transports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.js","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAe,EACf,aAAuB,EACvB,cAAwB,EACJ,EAAE;IACtB,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC;IAC/D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAEzE,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;AAC9E,CAAC,CAAC","sourcesContent":["/**\n * Pick the transport once both capability lists are known: the first entry\n * in the host's preference list that the client also supports. Both peers\n * compute the same result independently — no confirmation round trip.\n * Unknown identifiers are skipped, not fatal.\n */\nexport const selectTransportId = (\n isHost: boolean,\n ownTransports: string[],\n peerTransports: string[],\n): string | undefined => {\n const hostPreference = isHost ? ownTransports : peerTransports;\n const clientSupported = new Set(isHost ? peerTransports : ownTransports);\n\n return hostPreference.find(transportId => clientSupported.has(transportId));\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { selectTransportId } from "./transports.js";
|
|
3
|
+
describe("selectTransportId", () => {
|
|
4
|
+
it("picks the first host preference the client supports", () => {
|
|
5
|
+
expect(selectTransportId(true, ["ws", "wrtc"], ["wrtc", "ws"])).toBe("ws");
|
|
6
|
+
expect(selectTransportId(true, ["wrtc", "ws"], ["ws"])).toBe("ws");
|
|
7
|
+
});
|
|
8
|
+
it("computes the same result from both perspectives", () => {
|
|
9
|
+
const hostTransports = ["ws", "wrtc"];
|
|
10
|
+
const clientTransports = ["wrtc", "ws"];
|
|
11
|
+
expect(selectTransportId(true, hostTransports, clientTransports))
|
|
12
|
+
.toBe(selectTransportId(false, clientTransports, hostTransports));
|
|
13
|
+
});
|
|
14
|
+
it("skips identifiers the client has never heard of", () => {
|
|
15
|
+
expect(selectTransportId(true, ["quic", "wrtc"], ["wrtc"])).toBe("wrtc");
|
|
16
|
+
});
|
|
17
|
+
it("returns undefined when there is no common transport", () => {
|
|
18
|
+
expect(selectTransportId(true, ["ws"], ["wrtc"])).toBeUndefined();
|
|
19
|
+
expect(selectTransportId(true, ["wrtc"], [])).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=transports.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.test.js","sourceRoot":"","sources":["../src/transports.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3E,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAExC,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;aAC9D,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAClE,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, it } from \"vitest\";\n\nimport { selectTransportId } from \"./transports.js\";\n\ndescribe(\"selectTransportId\", () => {\n it(\"picks the first host preference the client supports\", () => {\n expect(selectTransportId(true, [\"ws\", \"wrtc\"], [\"wrtc\", \"ws\"])).toBe(\"ws\");\n expect(selectTransportId(true, [\"wrtc\", \"ws\"], [\"ws\"])).toBe(\"ws\");\n });\n\n it(\"computes the same result from both perspectives\", () => {\n const hostTransports = [\"ws\", \"wrtc\"];\n const clientTransports = [\"wrtc\", \"ws\"];\n\n expect(selectTransportId(true, hostTransports, clientTransports))\n .toBe(selectTransportId(false, clientTransports, hostTransports));\n });\n\n it(\"skips identifiers the client has never heard of\", () => {\n expect(selectTransportId(true, [\"quic\", \"wrtc\"], [\"wrtc\"])).toBe(\"wrtc\");\n });\n\n it(\"returns undefined when there is no common transport\", () => {\n expect(selectTransportId(true, [\"ws\"], [\"wrtc\"])).toBeUndefined();\n expect(selectTransportId(true, [\"wrtc\"], [])).toBeUndefined();\n });\n});\n"]}
|
package/dist/utils/log.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const log: (...
|
|
1
|
+
export declare const log: (...args: Parameters<typeof console.log>) => void;
|
package/dist/utils/log.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// "color: inherit; font-weight: normal",
|
|
4
|
-
...x);
|
|
1
|
+
import { createLogger } from "@openlv/core";
|
|
2
|
+
export const log = createLogger("session", "mediumpurple");
|
|
5
3
|
//# sourceMappingURL=log.js.map
|
package/dist/utils/log.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC","sourcesContent":["import { createLogger } from \"@openlv/core\";\n\nexport const log = createLogger(\"session\", \"mediumpurple\");\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlv/session",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"open-lavatory"
|
|
6
6
|
],
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/v3xlabs/open-lavatory.git",
|
|
13
|
+
"url": "git+https://github.com/v3xlabs/open-lavatory.git",
|
|
14
14
|
"directory": "packages/session"
|
|
15
15
|
},
|
|
16
16
|
"license": "LGPL-3.0-only",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"eventemitter3": "^5.0.1",
|
|
38
38
|
"globals": "^16.2.0",
|
|
39
39
|
"ts-pattern": "^5",
|
|
40
|
-
"@openlv/
|
|
41
|
-
"@openlv/
|
|
42
|
-
"@openlv/
|
|
40
|
+
"@openlv/core": "0.1.0",
|
|
41
|
+
"@openlv/signaling": "0.1.0",
|
|
42
|
+
"@openlv/transport": "0.1.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@vitest/browser-playwright": "^4.0.8",
|
|
@@ -47,14 +47,8 @@
|
|
|
47
47
|
"typescript": "^5",
|
|
48
48
|
"vitest": "^4"
|
|
49
49
|
},
|
|
50
|
-
"peerDependencies": {
|
|
51
|
-
"@wagmi/core": "2",
|
|
52
|
-
"viem": "2",
|
|
53
|
-
"wagmi": "2"
|
|
54
|
-
},
|
|
55
50
|
"publishConfig": {
|
|
56
|
-
"access": "public"
|
|
57
|
-
"tag": "beta"
|
|
51
|
+
"access": "public"
|
|
58
52
|
},
|
|
59
53
|
"scripts": {
|
|
60
54
|
"build": "tsc",
|