@openlv/session 0.0.1-beta.9 → 0.0.1
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/events.d.ts +7 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +55 -5
- package/dist/index.js +268 -6
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +5 -4
- package/dist/index.test.js.map +1 -1
- package/dist/messages/index.d.ts +10 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/utils/log.js.map +1 -1
- package/package.json +6 -12
- package/dist/base.d.ts +0 -48
- package/dist/base.js +0 -234
- package/dist/base.js.map +0 -1
package/dist/events.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type { SessionStateObject } from "./
|
|
1
|
+
import type { SessionStateObject } from "./index.js";
|
|
2
2
|
export type SessionEvents = {
|
|
3
3
|
state_change: (state?: SessionStateObject) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Emitted when a request arrives from the remote peer — before the reply
|
|
6
|
+
* is sent. Useful for observability, logging, or UI integration without
|
|
7
|
+
* needing to intercept the `onMessage` callback.
|
|
8
|
+
*/
|
|
9
|
+
request: (payload: object | string) => void;
|
|
4
10
|
};
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"","sourcesContent":["import type { SessionStateObject } from \"./index.js\";\n\nexport type SessionEvents = {\n state_change: (state?: SessionStateObject) => void;\n /**\n * Emitted when a request arrives from the remote peer — before the reply\n * is sent. Useful for observability, logging, or UI integration without\n * needing to intercept the `onMessage` callback.\n */\n request: (payload: object | string) => void;\n};\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
import { type SessionHandshakeParameters, type SessionLinkParameters } from "@openlv/core";
|
|
2
|
+
import { type CreateSignalLayerFn, type SignalingLayer, type SignalState } from "@openlv/signaling";
|
|
3
|
+
import { type TLayer, type TransportLayer } from "@openlv/transport";
|
|
4
|
+
import { EventEmitter } from "eventemitter3";
|
|
5
|
+
import type { SessionEvents } from "./events.js";
|
|
6
|
+
export declare const SESSION_STATE: {
|
|
7
|
+
readonly CREATED: "created";
|
|
8
|
+
readonly SIGNALING: "signaling";
|
|
9
|
+
readonly READY: "ready";
|
|
10
|
+
readonly LINKING: "linking";
|
|
11
|
+
readonly CONNECTED: "connected";
|
|
12
|
+
readonly DISCONNECTED: "disconnected";
|
|
13
|
+
};
|
|
14
|
+
export type SessionState = (typeof SESSION_STATE)[keyof typeof SESSION_STATE];
|
|
15
|
+
export type SessionStateObject = {
|
|
16
|
+
status: SessionState;
|
|
17
|
+
signaling?: {
|
|
18
|
+
state: SignalState;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
2
21
|
/**
|
|
3
|
-
*
|
|
22
|
+
* an OpenLV Session
|
|
4
23
|
*
|
|
5
|
-
*
|
|
6
|
-
* without relying on centralized infrastructure.
|
|
24
|
+
* https://openlv.sh/api/session
|
|
7
25
|
*/
|
|
8
|
-
export
|
|
26
|
+
export type Session = {
|
|
27
|
+
getState(): SessionStateObject;
|
|
28
|
+
getHandshakeParameters(): SessionHandshakeParameters;
|
|
29
|
+
connect(): Promise<void>;
|
|
30
|
+
waitForLink(): Promise<void>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Send a request to the remote peer and await a correlated response.
|
|
34
|
+
*
|
|
35
|
+
* Two-phase timeout:
|
|
36
|
+
* - `ackTimeout` (default 10 s): the remote peer must send back an ack
|
|
37
|
+
* within this window, confirming it received the message.
|
|
38
|
+
* - After an ack arrives the wait is extended to `responseTimeout`
|
|
39
|
+
* (default 1 hour) — enough for user-interactive flows such as
|
|
40
|
+
* `eth_sendTransaction` or `personal_sign`.
|
|
41
|
+
*/
|
|
42
|
+
send(message: object, ackTimeout?: number, responseTimeout?: number): Promise<unknown>;
|
|
43
|
+
emitter: EventEmitter<SessionEvents>;
|
|
44
|
+
_internal: {
|
|
45
|
+
signal: SignalingLayer;
|
|
46
|
+
transport: TransportLayer;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* OpenLV Session
|
|
51
|
+
*
|
|
52
|
+
* https://openlv.sh/api/session
|
|
53
|
+
*/
|
|
54
|
+
export declare const createSession: (initParameters: SessionLinkParameters, signalLayer: CreateSignalLayerFn, transportLayer: TLayer, onMessage: (message: object) => Promise<object | string>) => Promise<Session>;
|
|
55
|
+
/**
|
|
56
|
+
* Connect to a session from its openlv:// URL
|
|
57
|
+
*/
|
|
58
|
+
export declare const connectSession: (connectionUrl: string, onMessage: (message: object) => Promise<object | string>, transport: TLayer) => Promise<Session>;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,272 @@
|
|
|
1
|
-
|
|
1
|
+
import { decodeConnectionURL, OPENLV_PROTOCOL_VERSION, } from "@openlv/core";
|
|
2
|
+
import { deriveSymmetricKey, generateHandshakeKey, generateSessionId, initEncryptionKeys, initHash, parseEncryptionKey, } from "@openlv/core/encryption";
|
|
3
|
+
import { SIGNAL_STATE, } from "@openlv/signaling";
|
|
4
|
+
import { dynamicSignalingLayer } from "@openlv/signaling/dynamic";
|
|
5
|
+
import { TRANSPORT_STATE, } from "@openlv/transport";
|
|
6
|
+
import { EventEmitter } from "eventemitter3";
|
|
7
|
+
import { log } from "./utils/log.js";
|
|
8
|
+
export const SESSION_STATE = {
|
|
9
|
+
CREATED: "created",
|
|
10
|
+
SIGNALING: "signaling",
|
|
11
|
+
READY: "ready",
|
|
12
|
+
LINKING: "linking",
|
|
13
|
+
CONNECTED: "connected",
|
|
14
|
+
DISCONNECTED: "disconnected",
|
|
15
|
+
};
|
|
2
16
|
/**
|
|
3
|
-
*
|
|
17
|
+
* OpenLV Session
|
|
4
18
|
*
|
|
5
|
-
*
|
|
6
|
-
* without relying on centralized infrastructure.
|
|
19
|
+
* https://openlv.sh/api/session
|
|
7
20
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
21
|
+
export const createSession = async (initParameters, signalLayer, transportLayer, onMessage) => {
|
|
22
|
+
const emitter = new EventEmitter();
|
|
23
|
+
const messages = new EventEmitter();
|
|
24
|
+
const sessionId = "sessionId" in initParameters
|
|
25
|
+
? initParameters.sessionId
|
|
26
|
+
: generateSessionId();
|
|
27
|
+
const { encryptionKey, decryptionKey: { decrypt }, } = await initEncryptionKeys(initParameters);
|
|
28
|
+
let relyingPublicKey;
|
|
29
|
+
const handshakeKey = "k" in initParameters
|
|
30
|
+
? await deriveSymmetricKey(initParameters.k)
|
|
31
|
+
: await generateHandshakeKey();
|
|
32
|
+
const { hash, isHost } = await initHash("h" in initParameters ? initParameters.h : undefined, encryptionKey);
|
|
33
|
+
let status = SESSION_STATE.CREATED;
|
|
34
|
+
const protocol = initParameters.p;
|
|
35
|
+
const server = initParameters.s;
|
|
36
|
+
const updateStatus = (newStatus) => {
|
|
37
|
+
log("updateStatus", newStatus);
|
|
38
|
+
status = newStatus;
|
|
39
|
+
emitter.emit("state_change", { status, signaling: signal.getState() });
|
|
40
|
+
};
|
|
41
|
+
const signaling = await signalLayer({
|
|
42
|
+
topic: sessionId,
|
|
43
|
+
url: server,
|
|
44
|
+
});
|
|
45
|
+
const signal = await signaling({
|
|
46
|
+
h: hash,
|
|
47
|
+
canEncrypt() {
|
|
48
|
+
return relyingPublicKey !== undefined;
|
|
49
|
+
},
|
|
50
|
+
async encrypt(message) {
|
|
51
|
+
if (!relyingPublicKey) {
|
|
52
|
+
throw new Error("Relying party public key not found");
|
|
53
|
+
}
|
|
54
|
+
log(`encrypting to ${relyingPublicKey.toString()}`, message);
|
|
55
|
+
return await relyingPublicKey.encrypt(message);
|
|
56
|
+
},
|
|
57
|
+
decrypt,
|
|
58
|
+
publicKey: encryptionKey,
|
|
59
|
+
k: handshakeKey,
|
|
60
|
+
async rpDiscovered(rpKey) {
|
|
61
|
+
const role = isHost ? "host" : "client";
|
|
62
|
+
log(`rpKey discovered by ${role}`, rpKey);
|
|
63
|
+
relyingPublicKey = await parseEncryptionKey(rpKey);
|
|
64
|
+
},
|
|
65
|
+
isHost,
|
|
66
|
+
});
|
|
67
|
+
const transport = transportLayer({
|
|
68
|
+
encrypt(message) {
|
|
69
|
+
if (!relyingPublicKey) {
|
|
70
|
+
throw new Error("Relying party public key not found");
|
|
71
|
+
}
|
|
72
|
+
return relyingPublicKey?.encrypt(message);
|
|
73
|
+
},
|
|
74
|
+
decrypt,
|
|
75
|
+
isHost,
|
|
76
|
+
onmessage: async (message) => {
|
|
77
|
+
log("Session: received message from transport", message);
|
|
78
|
+
if (message["type"] === "request") {
|
|
79
|
+
const messageId = message["messageId"];
|
|
80
|
+
// Immediately acknowledge receipt so the sender's ack-timeout does
|
|
81
|
+
// not fire while the handler (which may await user interaction) runs.
|
|
82
|
+
await transport.send({ type: "ack", messageId });
|
|
83
|
+
// Notify observers before processing (e.g. wallet UI can show a
|
|
84
|
+
// pending indicator before the handler resolves).
|
|
85
|
+
emitter.emit("request", message["payload"]);
|
|
86
|
+
const data = await onMessage(message["payload"]);
|
|
87
|
+
const response = {
|
|
88
|
+
type: "response",
|
|
89
|
+
messageId,
|
|
90
|
+
payload: data,
|
|
91
|
+
};
|
|
92
|
+
await transport.send(response);
|
|
93
|
+
}
|
|
94
|
+
if (message["type"] === "response" || message["type"] === "ack") {
|
|
95
|
+
// Both acks and responses are forwarded to the send() correlator.
|
|
96
|
+
messages.emit("message", message);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
async subsend(message) {
|
|
100
|
+
log("Session: sending trans msg to signal", message);
|
|
101
|
+
const sessionMessage = {
|
|
102
|
+
type: "request",
|
|
103
|
+
messageId: crypto.randomUUID(),
|
|
104
|
+
payload: message,
|
|
105
|
+
};
|
|
106
|
+
await signal.send(sessionMessage);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
transport.emitter.on("state_change", (state) => {
|
|
110
|
+
log("transport state change", state);
|
|
111
|
+
if (state === TRANSPORT_STATE.CONNECTED) {
|
|
112
|
+
updateStatus(SESSION_STATE.CONNECTED);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const startTransport = async () => {
|
|
116
|
+
await transport.setup();
|
|
117
|
+
};
|
|
118
|
+
// let transport: TransportLayer | undefined;
|
|
119
|
+
const onSignalMessage = async (message) => {
|
|
120
|
+
log("Session: received message from signaling", message);
|
|
121
|
+
const sessionMsg = message;
|
|
122
|
+
if (sessionMsg.type === "response") {
|
|
123
|
+
log("Session: received data message", sessionMsg.payload);
|
|
124
|
+
messages.emit("message", sessionMsg);
|
|
125
|
+
}
|
|
126
|
+
if (sessionMsg.type === "request") {
|
|
127
|
+
log("Session: received request message", sessionMsg.payload);
|
|
128
|
+
await transport.handle(sessionMsg.payload);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
connect: async () => {
|
|
133
|
+
updateStatus(SESSION_STATE.SIGNALING);
|
|
134
|
+
log("connecting to session, isHost:", isHost);
|
|
135
|
+
// TODO: implement
|
|
136
|
+
log("connecting to session");
|
|
137
|
+
signal.on("message", onSignalMessage);
|
|
138
|
+
signal.on("state_change", (state) => {
|
|
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
|
|
158
|
+
await signal.setup();
|
|
159
|
+
},
|
|
160
|
+
async close() {
|
|
161
|
+
log("session teardown");
|
|
162
|
+
signal.off("message", onSignalMessage);
|
|
163
|
+
await Promise.all([
|
|
164
|
+
transport.teardown(),
|
|
165
|
+
signal.teardown(),
|
|
166
|
+
]);
|
|
167
|
+
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
168
|
+
},
|
|
169
|
+
getState() {
|
|
170
|
+
return {
|
|
171
|
+
status,
|
|
172
|
+
signaling: signal.getState(),
|
|
173
|
+
// transport: transport.getState(),
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
getHandshakeParameters() {
|
|
177
|
+
return {
|
|
178
|
+
version: OPENLV_PROTOCOL_VERSION,
|
|
179
|
+
sessionId,
|
|
180
|
+
h: hash,
|
|
181
|
+
k: handshakeKey.toString(),
|
|
182
|
+
p: protocol,
|
|
183
|
+
s: server,
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
waitForLink: async () => {
|
|
187
|
+
if (status === SESSION_STATE.CONNECTED)
|
|
188
|
+
return;
|
|
189
|
+
if (status === SESSION_STATE.DISCONNECTED) {
|
|
190
|
+
throw new Error("Session failed to connect");
|
|
191
|
+
}
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
const handler = (state) => {
|
|
194
|
+
if (state?.status === SESSION_STATE.CONNECTED) {
|
|
195
|
+
emitter.off("state_change", handler);
|
|
196
|
+
resolve();
|
|
197
|
+
}
|
|
198
|
+
else if (state?.status === SESSION_STATE.DISCONNECTED) {
|
|
199
|
+
emitter.off("state_change", handler);
|
|
200
|
+
reject(new Error("Session failed to connect"));
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
emitter.on("state_change", handler);
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
async send(message, ackTimeout = 10000, responseTimeout = 60 * 60000) {
|
|
207
|
+
if (signal.getState().state !== SIGNAL_STATE.ENCRYPTED) {
|
|
208
|
+
throw new Error("Session not ready");
|
|
209
|
+
}
|
|
210
|
+
const messageId = crypto.randomUUID();
|
|
211
|
+
const sessionMessage = {
|
|
212
|
+
type: "request",
|
|
213
|
+
messageId,
|
|
214
|
+
payload: message,
|
|
215
|
+
};
|
|
216
|
+
await transport.send(sessionMessage);
|
|
217
|
+
return new Promise((resolve, reject) => {
|
|
218
|
+
let ackReceived = false;
|
|
219
|
+
let ackTimer;
|
|
220
|
+
let responseTimer;
|
|
221
|
+
const cleanup = () => {
|
|
222
|
+
clearTimeout(ackTimer);
|
|
223
|
+
clearTimeout(responseTimer);
|
|
224
|
+
messages.off("message", handler);
|
|
225
|
+
};
|
|
226
|
+
const handler = (msg) => {
|
|
227
|
+
if (msg.messageId !== messageId)
|
|
228
|
+
return;
|
|
229
|
+
if (msg.type === "ack" && !ackReceived) {
|
|
230
|
+
ackReceived = true;
|
|
231
|
+
clearTimeout(ackTimer);
|
|
232
|
+
// The other side confirmed receipt; wait for the full response.
|
|
233
|
+
responseTimer = setTimeout(() => {
|
|
234
|
+
cleanup();
|
|
235
|
+
reject(new Error("Request timed out: no response after acknowledgement"));
|
|
236
|
+
}, responseTimeout);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (msg.type === "response") {
|
|
240
|
+
cleanup();
|
|
241
|
+
resolve(msg.payload);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
messages.on("message", handler);
|
|
245
|
+
// Short window for the ack — tells us the peer is alive and processing.
|
|
246
|
+
ackTimer = setTimeout(() => {
|
|
247
|
+
if (!ackReceived) {
|
|
248
|
+
cleanup();
|
|
249
|
+
reject(new Error("Request timed out: remote peer did not acknowledge"));
|
|
250
|
+
}
|
|
251
|
+
}, ackTimeout);
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
_internal: {
|
|
255
|
+
signal,
|
|
256
|
+
transport,
|
|
257
|
+
},
|
|
258
|
+
emitter,
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Connect to a session from its openlv:// URL
|
|
263
|
+
*/
|
|
264
|
+
export const connectSession = async (connectionUrl, onMessage, transport) => {
|
|
265
|
+
const initParameters = decodeConnectionURL(connectionUrl);
|
|
266
|
+
const signaling = await dynamicSignalingLayer(initParameters.p);
|
|
267
|
+
if (!signaling) {
|
|
268
|
+
throw new Error(`Invalid signaling protocol: ${initParameters.p}`);
|
|
269
|
+
}
|
|
270
|
+
return createSession(initParameters, signaling, transport, onMessage);
|
|
271
|
+
};
|
|
10
272
|
//# 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":"AAAA,yEAAyE;AAEzE;;;;;GAKG;AAEH,kEAAkE;AAClE,cAAc,WAAW,CAAC"}
|
|
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 CreateSignalLayerFn,\n SIGNAL_STATE,\n type SignalingLayer,\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: CreateSignalLayerFn,\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 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"]}
|
package/dist/index.test.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { decodeConnectionURL, encodeConnectionURL } from "@openlv/core";
|
|
2
2
|
import { ntfy } from "@openlv/signaling/ntfy";
|
|
3
|
+
import { webrtc } from "@openlv/transport/webrtc";
|
|
3
4
|
import { describe, expect, test } from "vitest";
|
|
4
|
-
import { connectSession, createSession } from "./
|
|
5
|
+
import { connectSession, createSession } from "./index.js";
|
|
5
6
|
describe("Session", () => {
|
|
6
7
|
test("Should be able to create a session", async () => {
|
|
7
8
|
const sessionA = await createSession({
|
|
8
9
|
sessionId: "mytestsession111",
|
|
9
10
|
p: "ntfy",
|
|
10
11
|
s: "https://ntfy.sh/",
|
|
11
|
-
}, ntfy, async (message) => {
|
|
12
|
+
}, ntfy, webrtc(), async (message) => {
|
|
12
13
|
console.log("sessionA received message", message);
|
|
13
14
|
return { result: "success" };
|
|
14
15
|
});
|
|
@@ -21,12 +22,12 @@ describe("Session", () => {
|
|
|
21
22
|
const decodedUrl = decodeConnectionURL(encodedUrl);
|
|
22
23
|
expect(decodedUrl).toEqual(handshakeParametersA);
|
|
23
24
|
//
|
|
24
|
-
await new Promise(
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
25
26
|
console.log("Connecting to session B");
|
|
26
27
|
const sessionB = await connectSession(encodedUrl, async (message) => {
|
|
27
28
|
console.log("sessionB received message", message);
|
|
28
29
|
return { result: "success" };
|
|
29
|
-
});
|
|
30
|
+
}, webrtc());
|
|
30
31
|
console.log(sessionB.getState());
|
|
31
32
|
await sessionB.connect();
|
|
32
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,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,
|
|
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,EACR,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,MAAM,EAAE,CACT,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"]}
|
package/dist/messages/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SessionMessage = SessionMessageRequest | SessionMessageResponse;
|
|
1
|
+
export type SessionMessage = SessionMessageRequest | SessionMessageResponse | SessionMessageAck;
|
|
2
2
|
export type SessionMessageRequest = {
|
|
3
3
|
type: "request";
|
|
4
4
|
messageId: string;
|
|
@@ -9,3 +9,12 @@ export type SessionMessageResponse = {
|
|
|
9
9
|
messageId: string;
|
|
10
10
|
payload: object | string;
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Sent immediately by the receiver of a request to confirm receipt.
|
|
14
|
+
* Allows the dApp to distinguish "wallet unreachable" from
|
|
15
|
+
* "wallet received and is processing (e.g. waiting for user approval)".
|
|
16
|
+
*/
|
|
17
|
+
export type SessionMessageAck = {
|
|
18
|
+
type: "ack";
|
|
19
|
+
messageId: string;
|
|
20
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"","sourcesContent":["export type SessionMessage =\n | SessionMessageRequest\n | SessionMessageResponse\n | SessionMessageAck;\n\nexport type SessionMessageRequest = {\n type: \"request\";\n messageId: string;\n payload: object | string;\n};\n\nexport type SessionMessageResponse = {\n type: \"response\";\n messageId: string;\n payload: object | string;\n};\n\n/**\n * Sent immediately by the receiver of a request to confirm receipt.\n * Allows the dApp to distinguish \"wallet unreachable\" from\n * \"wallet received and is processing (e.g. waiting for user approval)\".\n */\nexport type SessionMessageAck = {\n type: \"ack\";\n messageId: string;\n};\n"]}
|
package/dist/utils/log.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAiC,EAAE,EAAE,CAC1D,OAAO,CAAC,GAAG,CACT,WAAW;AACX,sCAAsC;AACtC,yCAAyC;AACzC,GAAG,CAAC,CACL,CAAC"}
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAiC,EAAE,EAAE,CAC1D,OAAO,CAAC,GAAG,CACT,WAAW;AACX,sCAAsC;AACtC,yCAAyC;AACzC,GAAG,CAAC,CACL,CAAC","sourcesContent":["export const log = (...x: Parameters<typeof console.log>) =>\n console.log(\n \"[session]\",\n // \"color: orange; font-weight: bold\",\n // \"color: inherit; font-weight: normal\",\n ...x,\n );\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlv/session",
|
|
3
|
-
"version": "0.0.1
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"open-lavatory"
|
|
6
6
|
],
|
|
@@ -25,11 +25,6 @@
|
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
26
|
"import": "./dist/index.js",
|
|
27
27
|
"default": "./dist/index.js"
|
|
28
|
-
},
|
|
29
|
-
"./provider": {
|
|
30
|
-
"types": "./dist/provider.d.ts",
|
|
31
|
-
"import": "./dist/provider.js",
|
|
32
|
-
"default": "./dist/provider.js"
|
|
33
28
|
}
|
|
34
29
|
},
|
|
35
30
|
"main": "./dist/index.js",
|
|
@@ -42,13 +37,13 @@
|
|
|
42
37
|
"eventemitter3": "^5.0.1",
|
|
43
38
|
"globals": "^16.2.0",
|
|
44
39
|
"ts-pattern": "^5",
|
|
45
|
-
"@openlv/core": "0.0.1
|
|
46
|
-
"@openlv/signaling": "0.0.1
|
|
47
|
-
"@openlv/transport": "0.0.1
|
|
40
|
+
"@openlv/core": "0.0.1",
|
|
41
|
+
"@openlv/signaling": "0.0.1",
|
|
42
|
+
"@openlv/transport": "0.0.1"
|
|
48
43
|
},
|
|
49
44
|
"devDependencies": {
|
|
50
45
|
"@vitest/browser-playwright": "^4.0.8",
|
|
51
|
-
"playwright": "
|
|
46
|
+
"playwright": "1.58.1",
|
|
52
47
|
"typescript": "^5",
|
|
53
48
|
"vitest": "^4"
|
|
54
49
|
},
|
|
@@ -63,7 +58,6 @@
|
|
|
63
58
|
},
|
|
64
59
|
"scripts": {
|
|
65
60
|
"build": "tsc",
|
|
66
|
-
"test": "vitest"
|
|
67
|
-
"test:install": "playwright install --with-deps"
|
|
61
|
+
"test": "vitest"
|
|
68
62
|
}
|
|
69
63
|
}
|
package/dist/base.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { type SessionHandshakeParameters, type SessionLinkParameters } from "@openlv/core";
|
|
2
|
-
import { type CreateSignalLayerFn, type SignalingLayer, type SignalState } from "@openlv/signaling";
|
|
3
|
-
import { type TransportLayer } from "@openlv/transport";
|
|
4
|
-
import { EventEmitter } from "eventemitter3";
|
|
5
|
-
import type { SessionEvents } from "./events.js";
|
|
6
|
-
export declare const SESSION_STATE: {
|
|
7
|
-
readonly CREATED: "created";
|
|
8
|
-
readonly SIGNALING: "signaling";
|
|
9
|
-
readonly READY: "ready";
|
|
10
|
-
readonly LINKING: "linking";
|
|
11
|
-
readonly CONNECTED: "connected";
|
|
12
|
-
readonly DISCONNECTED: "disconnected";
|
|
13
|
-
};
|
|
14
|
-
export type SessionState = (typeof SESSION_STATE)[keyof typeof SESSION_STATE];
|
|
15
|
-
export type SessionStateObject = {
|
|
16
|
-
status: SessionState;
|
|
17
|
-
signaling?: {
|
|
18
|
-
state: SignalState;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* an OpenLV Session
|
|
23
|
-
*
|
|
24
|
-
* https://openlv.sh/api/session
|
|
25
|
-
*/
|
|
26
|
-
export type Session = {
|
|
27
|
-
getState(): SessionStateObject;
|
|
28
|
-
getHandshakeParameters(): SessionHandshakeParameters;
|
|
29
|
-
connect(): Promise<void>;
|
|
30
|
-
waitForLink(): Promise<void>;
|
|
31
|
-
close(): Promise<void>;
|
|
32
|
-
send(message: object, timeout?: number): Promise<unknown>;
|
|
33
|
-
emitter: EventEmitter<SessionEvents>;
|
|
34
|
-
_internal: {
|
|
35
|
-
signal: SignalingLayer;
|
|
36
|
-
transport: TransportLayer;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* OpenLV Session
|
|
41
|
-
*
|
|
42
|
-
* https://openlv.sh/api/session
|
|
43
|
-
*/
|
|
44
|
-
export declare const createSession: (initParameters: SessionLinkParameters, signalLayer: CreateSignalLayerFn, onMessage: (message: object) => Promise<object | string>) => Promise<Session>;
|
|
45
|
-
/**
|
|
46
|
-
* Connect to a session from its openlv:// URL
|
|
47
|
-
*/
|
|
48
|
-
export declare const connectSession: (connectionUrl: string, onMessage: (message: object) => Promise<object | string>) => Promise<Session>;
|
package/dist/base.js
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { decodeConnectionURL, OPENLV_PROTOCOL_VERSION, } from "@openlv/core";
|
|
2
|
-
import { deriveSymmetricKey, generateHandshakeKey, generateSessionId, initEncryptionKeys, initHash, parseEncryptionKey, } from "@openlv/core/encryption";
|
|
3
|
-
import { SIGNAL_STATE, } from "@openlv/signaling";
|
|
4
|
-
import { dynamicSignalingLayer } from "@openlv/signaling/dynamic";
|
|
5
|
-
import { TRANSPORT_STATE, } from "@openlv/transport";
|
|
6
|
-
import { webrtc } from "@openlv/transport/webrtc";
|
|
7
|
-
import { EventEmitter } from "eventemitter3";
|
|
8
|
-
import { log } from "./utils/log.js";
|
|
9
|
-
export const SESSION_STATE = {
|
|
10
|
-
CREATED: "created",
|
|
11
|
-
SIGNALING: "signaling",
|
|
12
|
-
READY: "ready",
|
|
13
|
-
LINKING: "linking",
|
|
14
|
-
CONNECTED: "connected",
|
|
15
|
-
DISCONNECTED: "disconnected",
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* OpenLV Session
|
|
19
|
-
*
|
|
20
|
-
* https://openlv.sh/api/session
|
|
21
|
-
*/
|
|
22
|
-
export const createSession = async (initParameters, signalLayer, onMessage) => {
|
|
23
|
-
const emitter = new EventEmitter();
|
|
24
|
-
const messages = new EventEmitter();
|
|
25
|
-
const sessionId = "sessionId" in initParameters
|
|
26
|
-
? initParameters.sessionId
|
|
27
|
-
: generateSessionId();
|
|
28
|
-
const { encryptionKey, decryptionKey: { decrypt }, } = await initEncryptionKeys(initParameters);
|
|
29
|
-
let relyingPublicKey;
|
|
30
|
-
const handshakeKey = "k" in initParameters
|
|
31
|
-
? await deriveSymmetricKey(initParameters.k)
|
|
32
|
-
: await generateHandshakeKey();
|
|
33
|
-
const { hash, isHost } = await initHash("h" in initParameters ? initParameters.h : undefined, encryptionKey);
|
|
34
|
-
let status = SESSION_STATE.CREATED;
|
|
35
|
-
const protocol = initParameters.p;
|
|
36
|
-
const server = initParameters.s;
|
|
37
|
-
const updateStatus = (newStatus) => {
|
|
38
|
-
log("updateStatus", newStatus);
|
|
39
|
-
status = newStatus;
|
|
40
|
-
emitter.emit("state_change", { status, signaling: signal.getState() });
|
|
41
|
-
};
|
|
42
|
-
const signaling = await signalLayer({
|
|
43
|
-
topic: sessionId,
|
|
44
|
-
url: server,
|
|
45
|
-
});
|
|
46
|
-
const signal = await signaling({
|
|
47
|
-
h: hash,
|
|
48
|
-
canEncrypt() {
|
|
49
|
-
return relyingPublicKey !== undefined;
|
|
50
|
-
},
|
|
51
|
-
async encrypt(message) {
|
|
52
|
-
if (!relyingPublicKey) {
|
|
53
|
-
throw new Error("Relying party public key not found");
|
|
54
|
-
}
|
|
55
|
-
log(`encrypting to ${relyingPublicKey.toString()}`, message);
|
|
56
|
-
return await relyingPublicKey.encrypt(message);
|
|
57
|
-
},
|
|
58
|
-
decrypt,
|
|
59
|
-
publicKey: encryptionKey,
|
|
60
|
-
k: handshakeKey,
|
|
61
|
-
async rpDiscovered(rpKey) {
|
|
62
|
-
const role = isHost ? "host" : "client";
|
|
63
|
-
log(`rpKey discovered by ${role}`, rpKey);
|
|
64
|
-
relyingPublicKey = await parseEncryptionKey(rpKey);
|
|
65
|
-
},
|
|
66
|
-
isHost,
|
|
67
|
-
});
|
|
68
|
-
const transport = webrtc({ isHost })({
|
|
69
|
-
encrypt(message) {
|
|
70
|
-
if (!relyingPublicKey) {
|
|
71
|
-
throw new Error("Relying party public key not found");
|
|
72
|
-
}
|
|
73
|
-
return relyingPublicKey?.encrypt(message);
|
|
74
|
-
},
|
|
75
|
-
decrypt,
|
|
76
|
-
isHost,
|
|
77
|
-
onmessage: async (message) => {
|
|
78
|
-
console.log("Session: received message from transport", message);
|
|
79
|
-
// @ts-ignore
|
|
80
|
-
if (message["type"] === "request") {
|
|
81
|
-
// @ts-ignore
|
|
82
|
-
const data = await onMessage(message["payload"]);
|
|
83
|
-
const response = {
|
|
84
|
-
type: "response",
|
|
85
|
-
// @ts-ignore
|
|
86
|
-
messageId: message["messageId"],
|
|
87
|
-
payload: data,
|
|
88
|
-
};
|
|
89
|
-
await transport.send(response);
|
|
90
|
-
}
|
|
91
|
-
// @ts-ignore
|
|
92
|
-
if (message["type"] === "response") {
|
|
93
|
-
messages.emit("message", message);
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
async subsend(message) {
|
|
97
|
-
console.log("Session: sending trans msg to signal", message);
|
|
98
|
-
const sessionMessage = {
|
|
99
|
-
type: "request",
|
|
100
|
-
messageId: crypto.randomUUID(),
|
|
101
|
-
payload: message,
|
|
102
|
-
};
|
|
103
|
-
await signal.send(sessionMessage);
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
transport.emitter.on("state_change", (state) => {
|
|
107
|
-
log("transport state change", state);
|
|
108
|
-
if (state === TRANSPORT_STATE.CONNECTED) {
|
|
109
|
-
updateStatus(SESSION_STATE.CONNECTED);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
const startTransport = async () => {
|
|
113
|
-
await transport.setup();
|
|
114
|
-
};
|
|
115
|
-
// let transport: TransportLayer | undefined;
|
|
116
|
-
const onSignalMessage = async (message) => {
|
|
117
|
-
log("Session: received message from signaling", message);
|
|
118
|
-
const sessionMsg = message;
|
|
119
|
-
if (sessionMsg.type === "response") {
|
|
120
|
-
log("Session: received data message", sessionMsg.payload);
|
|
121
|
-
messages.emit("message", sessionMsg);
|
|
122
|
-
}
|
|
123
|
-
if (sessionMsg.type === "request") {
|
|
124
|
-
log("Session: received request message", sessionMsg.payload);
|
|
125
|
-
transport.handle(sessionMsg.payload);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
connect: async () => {
|
|
130
|
-
updateStatus(SESSION_STATE.SIGNALING);
|
|
131
|
-
log("connecting to session, isHost:", isHost);
|
|
132
|
-
// TODO: implement
|
|
133
|
-
log("connecting to session");
|
|
134
|
-
signal.on("message", onSignalMessage);
|
|
135
|
-
signal.on("state_change", (state) => {
|
|
136
|
-
log("signal state change", state);
|
|
137
|
-
if (state === SIGNAL_STATE.READY) {
|
|
138
|
-
updateStatus(SESSION_STATE.READY);
|
|
139
|
-
}
|
|
140
|
-
if ([
|
|
141
|
-
SIGNAL_STATE.HANDSHAKE,
|
|
142
|
-
SIGNAL_STATE.HANDSHAKE_PARTIAL,
|
|
143
|
-
].includes(state)) {
|
|
144
|
-
updateStatus(SESSION_STATE.LINKING);
|
|
145
|
-
}
|
|
146
|
-
if (state === SIGNAL_STATE.ENCRYPTED) {
|
|
147
|
-
// for now cuz we just use signaling call this a full connection
|
|
148
|
-
// updateStatus("connected");
|
|
149
|
-
startTransport();
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
// TODO: handle errors in setup nicely in modal UI
|
|
153
|
-
await signal.setup();
|
|
154
|
-
},
|
|
155
|
-
async close() {
|
|
156
|
-
log("session teardown");
|
|
157
|
-
await signal?.teardown();
|
|
158
|
-
updateStatus(SESSION_STATE.DISCONNECTED);
|
|
159
|
-
},
|
|
160
|
-
getState() {
|
|
161
|
-
return {
|
|
162
|
-
status,
|
|
163
|
-
signaling: signal.getState(),
|
|
164
|
-
// transport: transport.getState(),
|
|
165
|
-
};
|
|
166
|
-
},
|
|
167
|
-
getHandshakeParameters() {
|
|
168
|
-
return {
|
|
169
|
-
version: OPENLV_PROTOCOL_VERSION,
|
|
170
|
-
sessionId,
|
|
171
|
-
h: hash,
|
|
172
|
-
k: handshakeKey.toString(),
|
|
173
|
-
p: protocol,
|
|
174
|
-
s: server,
|
|
175
|
-
};
|
|
176
|
-
},
|
|
177
|
-
waitForLink: async () => {
|
|
178
|
-
return new Promise((resolve) => {
|
|
179
|
-
emitter.on("state_change", (state) => {
|
|
180
|
-
if (state?.status === SESSION_STATE.CONNECTED) {
|
|
181
|
-
resolve();
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
async send(message, timeout = 5000) {
|
|
187
|
-
const ready = signal.getState().state === SIGNAL_STATE.ENCRYPTED;
|
|
188
|
-
// const transportReady = transport.getState() === TRANSPORT_STATE.CONNECTED;
|
|
189
|
-
if (!ready) {
|
|
190
|
-
throw new Error("Session not ready");
|
|
191
|
-
}
|
|
192
|
-
const randomID = crypto.randomUUID();
|
|
193
|
-
const sessionMessage = {
|
|
194
|
-
type: "request",
|
|
195
|
-
messageId: randomID,
|
|
196
|
-
payload: message,
|
|
197
|
-
};
|
|
198
|
-
// for now use signaling
|
|
199
|
-
// await signal.send(sessionMessage);
|
|
200
|
-
await transport.send(sessionMessage);
|
|
201
|
-
return Promise.race([
|
|
202
|
-
new Promise((resolve) => {
|
|
203
|
-
messages.on("message", (message) => {
|
|
204
|
-
if (message.messageId === randomID && message.type === "response") {
|
|
205
|
-
resolve(message.payload);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
}),
|
|
209
|
-
new Promise((resolve) => {
|
|
210
|
-
setTimeout(() => {
|
|
211
|
-
resolve(new Error("Timeout"));
|
|
212
|
-
}, timeout);
|
|
213
|
-
}),
|
|
214
|
-
]);
|
|
215
|
-
},
|
|
216
|
-
_internal: {
|
|
217
|
-
signal,
|
|
218
|
-
transport,
|
|
219
|
-
},
|
|
220
|
-
emitter,
|
|
221
|
-
};
|
|
222
|
-
};
|
|
223
|
-
/**
|
|
224
|
-
* Connect to a session from its openlv:// URL
|
|
225
|
-
*/
|
|
226
|
-
export const connectSession = async (connectionUrl, onMessage) => {
|
|
227
|
-
const initParameters = decodeConnectionURL(connectionUrl);
|
|
228
|
-
const signaling = await dynamicSignalingLayer(initParameters.p);
|
|
229
|
-
if (!signaling) {
|
|
230
|
-
throw new Error(`Invalid signaling protocol: ${initParameters.p}`);
|
|
231
|
-
}
|
|
232
|
-
return createSession(initParameters, signaling, onMessage);
|
|
233
|
-
};
|
|
234
|
-
//# sourceMappingURL=base.js.map
|
package/dist/base.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"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"}
|