@byoky/sdk 0.2.0 → 0.4.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/LICENSE +21 -0
- package/dist/index.cjs +290 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -4
- package/dist/index.d.ts +28 -4
- package/dist/index.js +293 -9
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +223 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +28 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.js +202 -0
- package/dist/server.js.map +1 -0
- package/package.json +29 -10
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/byoky.ts
|
|
2
|
-
import { ByokyError, ByokyErrorCode } from "@byoky/core";
|
|
2
|
+
import { ByokyError as ByokyError2, ByokyErrorCode } from "@byoky/core";
|
|
3
3
|
|
|
4
4
|
// src/detect.ts
|
|
5
5
|
import { BYOKY_PROVIDER_KEY } from "@byoky/core";
|
|
@@ -130,6 +130,189 @@ async function readBody(body) {
|
|
|
130
130
|
return void 0;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// src/relay-client.ts
|
|
134
|
+
import {
|
|
135
|
+
ByokyError,
|
|
136
|
+
parseRelayMessage,
|
|
137
|
+
WS_READY_STATE
|
|
138
|
+
} from "@byoky/core";
|
|
139
|
+
function createRelayClient(wsUrl, sessionKey, providers) {
|
|
140
|
+
let status = "connecting";
|
|
141
|
+
const closeCallbacks = /* @__PURE__ */ new Set();
|
|
142
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
143
|
+
let ws;
|
|
144
|
+
let pingInterval;
|
|
145
|
+
try {
|
|
146
|
+
const parsed = new URL(wsUrl);
|
|
147
|
+
const isSecure = parsed.protocol === "wss:";
|
|
148
|
+
const isLocalWs = parsed.protocol === "ws:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
149
|
+
if (!isSecure && !isLocalWs) {
|
|
150
|
+
status = "disconnected";
|
|
151
|
+
return {
|
|
152
|
+
get status() {
|
|
153
|
+
return status;
|
|
154
|
+
},
|
|
155
|
+
close() {
|
|
156
|
+
},
|
|
157
|
+
onClose(cb) {
|
|
158
|
+
cb("Insecure WebSocket URL rejected \u2014 use wss:// for non-localhost connections");
|
|
159
|
+
return () => {
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
status = "disconnected";
|
|
166
|
+
return {
|
|
167
|
+
get status() {
|
|
168
|
+
return status;
|
|
169
|
+
},
|
|
170
|
+
close() {
|
|
171
|
+
},
|
|
172
|
+
onClose(cb) {
|
|
173
|
+
cb("Invalid WebSocket URL");
|
|
174
|
+
return () => {
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
ws = new WebSocket(wsUrl);
|
|
181
|
+
} catch {
|
|
182
|
+
status = "disconnected";
|
|
183
|
+
return {
|
|
184
|
+
get status() {
|
|
185
|
+
return status;
|
|
186
|
+
},
|
|
187
|
+
close() {
|
|
188
|
+
},
|
|
189
|
+
onClose(cb) {
|
|
190
|
+
cb("Failed to create WebSocket");
|
|
191
|
+
return () => {
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
ws.onopen = () => {
|
|
197
|
+
status = "connected";
|
|
198
|
+
const relayId = `relay_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
199
|
+
ws.send(JSON.stringify({
|
|
200
|
+
type: "relay:hello",
|
|
201
|
+
sessionId: relayId,
|
|
202
|
+
providers
|
|
203
|
+
}));
|
|
204
|
+
pingInterval = setInterval(() => {
|
|
205
|
+
if (ws.readyState === WS_READY_STATE.OPEN) {
|
|
206
|
+
ws.send(JSON.stringify({ type: "relay:ping", ts: Date.now() }));
|
|
207
|
+
}
|
|
208
|
+
}, 3e4);
|
|
209
|
+
};
|
|
210
|
+
ws.onmessage = (event) => {
|
|
211
|
+
const msg = parseRelayMessage(event.data);
|
|
212
|
+
if (!msg) return;
|
|
213
|
+
switch (msg.type) {
|
|
214
|
+
case "relay:request":
|
|
215
|
+
handleRequest(msg);
|
|
216
|
+
break;
|
|
217
|
+
case "relay:ping":
|
|
218
|
+
ws.send(JSON.stringify({ type: "relay:pong", ts: msg.ts }));
|
|
219
|
+
break;
|
|
220
|
+
case "relay:pong":
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
ws.onclose = (event) => {
|
|
225
|
+
cleanup(event.reason || "WebSocket closed");
|
|
226
|
+
};
|
|
227
|
+
ws.onerror = () => {
|
|
228
|
+
cleanup("WebSocket error");
|
|
229
|
+
};
|
|
230
|
+
async function handleRequest(req) {
|
|
231
|
+
const { requestId, providerId, url, method, headers, body } = req;
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
inFlight.set(requestId, controller);
|
|
234
|
+
try {
|
|
235
|
+
const proxyFetch = createProxyFetch(providerId, sessionKey);
|
|
236
|
+
const response = await proxyFetch(url, {
|
|
237
|
+
method,
|
|
238
|
+
headers,
|
|
239
|
+
body,
|
|
240
|
+
signal: controller.signal
|
|
241
|
+
});
|
|
242
|
+
const responseHeaders = {};
|
|
243
|
+
response.headers.forEach((value, key) => {
|
|
244
|
+
responseHeaders[key] = value;
|
|
245
|
+
});
|
|
246
|
+
send({
|
|
247
|
+
type: "relay:response:meta",
|
|
248
|
+
requestId,
|
|
249
|
+
status: response.status,
|
|
250
|
+
statusText: response.statusText,
|
|
251
|
+
headers: responseHeaders
|
|
252
|
+
});
|
|
253
|
+
if (response.body) {
|
|
254
|
+
const reader = response.body.getReader();
|
|
255
|
+
const decoder = new TextDecoder();
|
|
256
|
+
while (true) {
|
|
257
|
+
const { done, value } = await reader.read();
|
|
258
|
+
if (done) break;
|
|
259
|
+
if (controller.signal.aborted) break;
|
|
260
|
+
send({
|
|
261
|
+
type: "relay:response:chunk",
|
|
262
|
+
requestId,
|
|
263
|
+
chunk: decoder.decode(value, { stream: true })
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
send({ type: "relay:response:done", requestId });
|
|
268
|
+
} catch (err) {
|
|
269
|
+
send({
|
|
270
|
+
type: "relay:response:error",
|
|
271
|
+
requestId,
|
|
272
|
+
error: {
|
|
273
|
+
code: err instanceof ByokyError ? err.code : "PROXY_ERROR",
|
|
274
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
} finally {
|
|
278
|
+
inFlight.delete(requestId);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function send(msg) {
|
|
282
|
+
if (ws.readyState === WS_READY_STATE.OPEN) {
|
|
283
|
+
ws.send(JSON.stringify(msg));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function cleanup(reason) {
|
|
287
|
+
if (status === "disconnected") return;
|
|
288
|
+
status = "disconnected";
|
|
289
|
+
if (pingInterval) clearInterval(pingInterval);
|
|
290
|
+
for (const controller of inFlight.values()) {
|
|
291
|
+
controller.abort();
|
|
292
|
+
}
|
|
293
|
+
inFlight.clear();
|
|
294
|
+
for (const cb of closeCallbacks) cb(reason);
|
|
295
|
+
closeCallbacks.clear();
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
get status() {
|
|
299
|
+
return status;
|
|
300
|
+
},
|
|
301
|
+
close() {
|
|
302
|
+
if (ws.readyState === WS_READY_STATE.OPEN || ws.readyState === WS_READY_STATE.CONNECTING) {
|
|
303
|
+
ws.close(1e3, "Client closed");
|
|
304
|
+
}
|
|
305
|
+
cleanup("Client closed");
|
|
306
|
+
},
|
|
307
|
+
onClose(callback) {
|
|
308
|
+
closeCallbacks.add(callback);
|
|
309
|
+
return () => {
|
|
310
|
+
closeCallbacks.delete(callback);
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
133
316
|
// src/byoky.ts
|
|
134
317
|
var Byoky = class {
|
|
135
318
|
timeout;
|
|
@@ -137,18 +320,61 @@ var Byoky = class {
|
|
|
137
320
|
this.timeout = options.timeout ?? 6e4;
|
|
138
321
|
}
|
|
139
322
|
async connect(request = {}) {
|
|
323
|
+
if (typeof window === "undefined") {
|
|
324
|
+
throw new ByokyError2(
|
|
325
|
+
ByokyErrorCode.UNKNOWN,
|
|
326
|
+
"Byoky SDK requires a browser environment. On the server, use your API key directly."
|
|
327
|
+
);
|
|
328
|
+
}
|
|
140
329
|
if (!isExtensionInstalled()) {
|
|
141
330
|
const storeUrl = getStoreUrl();
|
|
142
331
|
if (storeUrl) {
|
|
143
332
|
window.open(storeUrl, "_blank");
|
|
144
333
|
}
|
|
145
|
-
throw
|
|
334
|
+
throw ByokyError2.walletNotInstalled();
|
|
146
335
|
}
|
|
147
336
|
const response = await this.sendConnectRequest(request);
|
|
337
|
+
return this.buildSession(response);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Reconnect to an existing session using previously stored response data.
|
|
341
|
+
* Returns null if the session is no longer valid.
|
|
342
|
+
*/
|
|
343
|
+
async reconnect(savedResponse) {
|
|
344
|
+
if (typeof window === "undefined") return null;
|
|
345
|
+
if (!isExtensionInstalled()) return null;
|
|
346
|
+
const connected = await this.querySessionStatus(savedResponse.sessionKey);
|
|
347
|
+
if (!connected) return null;
|
|
348
|
+
return this.buildSession(savedResponse);
|
|
349
|
+
}
|
|
350
|
+
buildSession(response) {
|
|
351
|
+
const sessionKey = response.sessionKey;
|
|
352
|
+
const disconnectCallbacks = /* @__PURE__ */ new Set();
|
|
353
|
+
function handleRevocation(event) {
|
|
354
|
+
const msg = event.detail;
|
|
355
|
+
if (msg?.type === "BYOKY_SESSION_REVOKED" && msg.payload?.sessionKey === sessionKey) {
|
|
356
|
+
for (const cb of disconnectCallbacks) cb();
|
|
357
|
+
disconnectCallbacks.clear();
|
|
358
|
+
document.removeEventListener("byoky-message", handleRevocation);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
document.addEventListener("byoky-message", handleRevocation);
|
|
148
362
|
return {
|
|
149
363
|
...response,
|
|
150
|
-
createFetch: (providerId) => createProxyFetch(providerId,
|
|
151
|
-
|
|
364
|
+
createFetch: (providerId) => createProxyFetch(providerId, sessionKey),
|
|
365
|
+
createRelay: (wsUrl) => createRelayClient(wsUrl, sessionKey, response.providers),
|
|
366
|
+
disconnect: () => {
|
|
367
|
+
document.removeEventListener("byoky-message", handleRevocation);
|
|
368
|
+
this.sendDisconnect(sessionKey);
|
|
369
|
+
},
|
|
370
|
+
isConnected: () => this.querySessionStatus(sessionKey),
|
|
371
|
+
getUsage: () => this.querySessionUsage(sessionKey),
|
|
372
|
+
onDisconnect: (callback) => {
|
|
373
|
+
disconnectCallbacks.add(callback);
|
|
374
|
+
return () => {
|
|
375
|
+
disconnectCallbacks.delete(callback);
|
|
376
|
+
};
|
|
377
|
+
}
|
|
152
378
|
};
|
|
153
379
|
}
|
|
154
380
|
sendConnectRequest(request) {
|
|
@@ -157,7 +383,7 @@ var Byoky = class {
|
|
|
157
383
|
const timeoutId = setTimeout(() => {
|
|
158
384
|
cleanup();
|
|
159
385
|
reject(
|
|
160
|
-
new
|
|
386
|
+
new ByokyError2(ByokyErrorCode.UNKNOWN, "Connection request timed out")
|
|
161
387
|
);
|
|
162
388
|
}, this.timeout);
|
|
163
389
|
function handleEvent(event) {
|
|
@@ -169,7 +395,7 @@ var Byoky = class {
|
|
|
169
395
|
resolve(msg.payload);
|
|
170
396
|
} else if (msg.type === "BYOKY_ERROR") {
|
|
171
397
|
const { code, message } = msg.payload;
|
|
172
|
-
reject(new
|
|
398
|
+
reject(new ByokyError2(code, message));
|
|
173
399
|
}
|
|
174
400
|
}
|
|
175
401
|
function cleanup() {
|
|
@@ -188,22 +414,80 @@ var Byoky = class {
|
|
|
188
414
|
);
|
|
189
415
|
});
|
|
190
416
|
}
|
|
191
|
-
|
|
417
|
+
sendDisconnect(sessionKey) {
|
|
192
418
|
window.postMessage(
|
|
193
419
|
{ type: "BYOKY_DISCONNECT", payload: { sessionKey } },
|
|
194
420
|
"*"
|
|
195
421
|
);
|
|
196
422
|
}
|
|
423
|
+
querySessionStatus(sessionKey) {
|
|
424
|
+
return new Promise((resolve) => {
|
|
425
|
+
const requestId = crypto.randomUUID();
|
|
426
|
+
const timeout = setTimeout(() => {
|
|
427
|
+
cleanup();
|
|
428
|
+
resolve(false);
|
|
429
|
+
}, 5e3);
|
|
430
|
+
function handleEvent(event) {
|
|
431
|
+
const msg = event.detail;
|
|
432
|
+
if (msg?.requestId !== requestId) return;
|
|
433
|
+
if (msg.type === "BYOKY_SESSION_STATUS_RESPONSE") {
|
|
434
|
+
cleanup();
|
|
435
|
+
resolve(!!msg.payload?.connected);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function cleanup() {
|
|
439
|
+
clearTimeout(timeout);
|
|
440
|
+
document.removeEventListener("byoky-message", handleEvent);
|
|
441
|
+
}
|
|
442
|
+
document.addEventListener("byoky-message", handleEvent);
|
|
443
|
+
window.postMessage({
|
|
444
|
+
type: "BYOKY_SESSION_STATUS",
|
|
445
|
+
requestId,
|
|
446
|
+
payload: { sessionKey }
|
|
447
|
+
}, "*");
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
querySessionUsage(sessionKey) {
|
|
451
|
+
return new Promise((resolve, reject) => {
|
|
452
|
+
const requestId = crypto.randomUUID();
|
|
453
|
+
const timeout = setTimeout(() => {
|
|
454
|
+
cleanup();
|
|
455
|
+
reject(new Error("Usage query timed out"));
|
|
456
|
+
}, 5e3);
|
|
457
|
+
function handleEvent(event) {
|
|
458
|
+
const msg = event.detail;
|
|
459
|
+
if (msg?.requestId !== requestId) return;
|
|
460
|
+
if (msg.type === "BYOKY_SESSION_USAGE_RESPONSE") {
|
|
461
|
+
cleanup();
|
|
462
|
+
if (msg.payload) {
|
|
463
|
+
resolve(msg.payload);
|
|
464
|
+
} else {
|
|
465
|
+
reject(new Error("Session not found"));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function cleanup() {
|
|
470
|
+
clearTimeout(timeout);
|
|
471
|
+
document.removeEventListener("byoky-message", handleEvent);
|
|
472
|
+
}
|
|
473
|
+
document.addEventListener("byoky-message", handleEvent);
|
|
474
|
+
window.postMessage({
|
|
475
|
+
type: "BYOKY_SESSION_USAGE",
|
|
476
|
+
requestId,
|
|
477
|
+
payload: { sessionKey }
|
|
478
|
+
}, "*");
|
|
479
|
+
});
|
|
480
|
+
}
|
|
197
481
|
};
|
|
198
482
|
|
|
199
483
|
// src/index.ts
|
|
200
484
|
import {
|
|
201
|
-
ByokyError as
|
|
485
|
+
ByokyError as ByokyError3,
|
|
202
486
|
ByokyErrorCode as ByokyErrorCode2
|
|
203
487
|
} from "@byoky/core";
|
|
204
488
|
export {
|
|
205
489
|
Byoky,
|
|
206
|
-
|
|
490
|
+
ByokyError3 as ByokyError,
|
|
207
491
|
ByokyErrorCode2 as ByokyErrorCode,
|
|
208
492
|
createProxyFetch,
|
|
209
493
|
getStoreUrl,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/byoky.ts","../src/detect.ts","../src/proxy-fetch.ts","../src/index.ts"],"sourcesContent":["import type { ConnectRequest, ConnectResponse } from '@byoky/core';\nimport { ByokyError, ByokyErrorCode, isByokyMessage } from '@byoky/core';\nimport { isExtensionInstalled, getStoreUrl } from './detect.js';\nimport { createProxyFetch } from './proxy-fetch.js';\n\nexport interface ByokySession extends ConnectResponse {\n createFetch(providerId: string): typeof fetch;\n disconnect(): void;\n}\n\nexport interface ByokyOptions {\n timeout?: number;\n}\n\nexport class Byoky {\n private timeout: number;\n\n constructor(options: ByokyOptions = {}) {\n this.timeout = options.timeout ?? 60_000;\n }\n\n async connect(request: ConnectRequest = {}): Promise<ByokySession> {\n if (!isExtensionInstalled()) {\n const storeUrl = getStoreUrl();\n if (storeUrl) {\n window.open(storeUrl, '_blank');\n }\n throw ByokyError.walletNotInstalled();\n }\n\n const response = await this.sendConnectRequest(request);\n\n return {\n ...response,\n createFetch: (providerId: string) =>\n createProxyFetch(providerId, response.sessionKey),\n disconnect: () => this.disconnect(response.sessionKey),\n };\n }\n\n private sendConnectRequest(\n request: ConnectRequest,\n ): Promise<ConnectResponse> {\n return new Promise<ConnectResponse>((resolve, reject) => {\n const requestId = crypto.randomUUID();\n\n const timeoutId = setTimeout(() => {\n cleanup();\n reject(\n new ByokyError(ByokyErrorCode.UNKNOWN, 'Connection request timed out'),\n );\n }, this.timeout);\n\n function handleEvent(event: Event) {\n const msg = (event as CustomEvent).detail;\n if (typeof msg?.type !== 'string' || !msg.type.startsWith('BYOKY_')) return;\n if (msg.requestId !== requestId) return;\n\n cleanup();\n\n if (msg.type === 'BYOKY_CONNECT_RESPONSE') {\n resolve(msg.payload as ConnectResponse);\n } else if (msg.type === 'BYOKY_ERROR') {\n const { code, message } = msg.payload as {\n code: string;\n message: string;\n };\n reject(new ByokyError(code as ByokyErrorCode, message));\n }\n }\n\n function cleanup() {\n clearTimeout(timeoutId);\n document.removeEventListener('byoky-message', handleEvent);\n }\n\n document.addEventListener('byoky-message', handleEvent);\n\n window.postMessage(\n {\n type: 'BYOKY_CONNECT_REQUEST',\n id: requestId,\n requestId,\n payload: request,\n },\n '*',\n );\n });\n }\n\n private disconnect(sessionKey: string): void {\n window.postMessage(\n { type: 'BYOKY_DISCONNECT', payload: { sessionKey } },\n '*',\n );\n }\n}\n","import { BYOKY_PROVIDER_KEY } from '@byoky/core';\n\nexport function isExtensionInstalled(): boolean {\n return typeof window !== 'undefined' && BYOKY_PROVIDER_KEY in window;\n}\n\nexport function getStoreUrl(): string | null {\n if (typeof navigator === 'undefined') return null;\n\n const ua = navigator.userAgent.toLowerCase();\n\n if (ua.includes('chrome') && !ua.includes('edg')) {\n return 'https://chrome.google.com/webstore/detail/byoky/TODO_EXTENSION_ID';\n }\n if (ua.includes('firefox')) {\n return 'https://addons.mozilla.org/en-US/firefox/addon/byoky/';\n }\n if (ua.includes('safari') && !ua.includes('chrome')) {\n return 'https://apps.apple.com/app/byoky/TODO_APP_ID';\n }\n return null;\n}\n","export function createProxyFetch(\n providerId: string,\n sessionKey: string,\n): typeof fetch {\n return async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.toString()\n : input.url;\n\n const method = init?.method ?? 'GET';\n const headers = init?.headers\n ? Object.fromEntries(new Headers(init.headers).entries())\n : {};\n const body = init?.body ? await readBody(init.body) : undefined;\n\n const requestId = crypto.randomUUID();\n\n return new Promise<Response>((resolve, reject) => {\n const { readable, writable } = new TransformStream<Uint8Array>();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n let resolved = false;\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error('Proxy request timed out'));\n }, 120_000);\n\n function handleEvent(event: Event) {\n const data = (event as CustomEvent).detail ?? (event as MessageEvent).data;\n if (data?.requestId !== requestId) return;\n\n switch (data.type) {\n case 'BYOKY_PROXY_RESPONSE_META':\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolve(\n new Response(readable, {\n status: data.status,\n statusText: data.statusText,\n headers: new Headers(data.headers),\n }),\n );\n }\n break;\n\n case 'BYOKY_PROXY_RESPONSE_CHUNK':\n writer.write(encoder.encode(data.chunk)).catch(() => {});\n break;\n\n case 'BYOKY_PROXY_RESPONSE_DONE':\n writer.close().catch(() => {});\n cleanup();\n break;\n\n case 'BYOKY_PROXY_RESPONSE_ERROR': {\n const errResponse = new Response(\n JSON.stringify({ error: data.error }),\n {\n status: data.status || 500,\n headers: { 'content-type': 'application/json' },\n },\n );\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolve(errResponse);\n }\n writer.close().catch(() => {});\n cleanup();\n break;\n }\n }\n }\n\n function cleanup() {\n clearTimeout(timeout);\n document.removeEventListener('byoky-message', handleEvent);\n }\n\n document.addEventListener('byoky-message', handleEvent);\n\n window.postMessage(\n {\n type: 'BYOKY_PROXY_REQUEST',\n requestId,\n sessionKey,\n providerId,\n url,\n method,\n headers,\n body,\n },\n '*',\n );\n });\n };\n}\n\nasync function readBody(body: BodyInit): Promise<string | undefined> {\n if (typeof body === 'string') return body;\n if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);\n if (body instanceof Blob) return body.text();\n if (body instanceof URLSearchParams) return body.toString();\n if (body instanceof ReadableStream) {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n const total = chunks.reduce((acc, c) => acc + c.length, 0);\n const combined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n return new TextDecoder().decode(combined);\n }\n return undefined;\n}\n","export { Byoky } from './byoky.js';\nexport type { ByokySession, ByokyOptions } from './byoky.js';\nexport { isExtensionInstalled, getStoreUrl } from './detect.js';\nexport { createProxyFetch } from './proxy-fetch.js';\nexport {\n type ConnectRequest,\n type ConnectResponse,\n type ProviderRequirement,\n ByokyError,\n ByokyErrorCode,\n} from '@byoky/core';\n"],"mappings":"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","names":["ByokyError","ByokyErrorCode"]}
|
|
1
|
+
{"version":3,"sources":["../src/byoky.ts","../src/detect.ts","../src/proxy-fetch.ts","../src/relay-client.ts","../src/index.ts"],"sourcesContent":["import type { ConnectRequest, ConnectResponse, SessionUsage } from '@byoky/core';\nimport { ByokyError, ByokyErrorCode, isByokyMessage } from '@byoky/core';\nimport { isExtensionInstalled, getStoreUrl } from './detect.js';\nimport { createProxyFetch } from './proxy-fetch.js';\nimport { createRelayClient, type RelayConnection } from './relay-client.js';\n\nexport interface ByokySession extends ConnectResponse {\n /** Create a fetch function that proxies requests through the wallet for the given provider. */\n createFetch(providerId: string): typeof fetch;\n /** Open a relay channel so a backend server can make LLM calls through this session. */\n createRelay(wsUrl: string): RelayConnection;\n /** Disconnect this session from the wallet. */\n disconnect(): void;\n /** Check if this session is still connected and valid. */\n isConnected(): Promise<boolean>;\n /** Get token usage stats for this session only. */\n getUsage(): Promise<SessionUsage>;\n /** Register a callback for when the wallet revokes this session. */\n onDisconnect(callback: () => void): () => void;\n}\n\nexport interface ByokyOptions {\n timeout?: number;\n}\n\nexport class Byoky {\n private timeout: number;\n\n constructor(options: ByokyOptions = {}) {\n this.timeout = options.timeout ?? 60_000;\n }\n\n async connect(request: ConnectRequest = {}): Promise<ByokySession> {\n if (typeof window === 'undefined') {\n throw new ByokyError(\n ByokyErrorCode.UNKNOWN,\n 'Byoky SDK requires a browser environment. On the server, use your API key directly.',\n );\n }\n\n if (!isExtensionInstalled()) {\n const storeUrl = getStoreUrl();\n if (storeUrl) {\n window.open(storeUrl, '_blank');\n }\n throw ByokyError.walletNotInstalled();\n }\n\n const response = await this.sendConnectRequest(request);\n return this.buildSession(response);\n }\n\n /**\n * Reconnect to an existing session using previously stored response data.\n * Returns null if the session is no longer valid.\n */\n async reconnect(savedResponse: ConnectResponse): Promise<ByokySession | null> {\n if (typeof window === 'undefined') return null;\n if (!isExtensionInstalled()) return null;\n\n const connected = await this.querySessionStatus(savedResponse.sessionKey);\n if (!connected) return null;\n\n return this.buildSession(savedResponse);\n }\n\n private buildSession(response: ConnectResponse): ByokySession {\n const sessionKey = response.sessionKey;\n const disconnectCallbacks = new Set<() => void>();\n\n function handleRevocation(event: Event) {\n const msg = (event as CustomEvent).detail;\n if (msg?.type === 'BYOKY_SESSION_REVOKED' && msg.payload?.sessionKey === sessionKey) {\n for (const cb of disconnectCallbacks) cb();\n disconnectCallbacks.clear();\n document.removeEventListener('byoky-message', handleRevocation);\n }\n }\n document.addEventListener('byoky-message', handleRevocation);\n\n return {\n ...response,\n createFetch: (providerId: string) =>\n createProxyFetch(providerId, sessionKey),\n createRelay: (wsUrl: string) =>\n createRelayClient(wsUrl, sessionKey, response.providers),\n disconnect: () => {\n document.removeEventListener('byoky-message', handleRevocation);\n this.sendDisconnect(sessionKey);\n },\n isConnected: () => this.querySessionStatus(sessionKey),\n getUsage: () => this.querySessionUsage(sessionKey),\n onDisconnect: (callback: () => void) => {\n disconnectCallbacks.add(callback);\n return () => { disconnectCallbacks.delete(callback); };\n },\n };\n }\n\n private sendConnectRequest(\n request: ConnectRequest,\n ): Promise<ConnectResponse> {\n return new Promise<ConnectResponse>((resolve, reject) => {\n const requestId = crypto.randomUUID();\n\n const timeoutId = setTimeout(() => {\n cleanup();\n reject(\n new ByokyError(ByokyErrorCode.UNKNOWN, 'Connection request timed out'),\n );\n }, this.timeout);\n\n function handleEvent(event: Event) {\n const msg = (event as CustomEvent).detail;\n if (typeof msg?.type !== 'string' || !msg.type.startsWith('BYOKY_')) return;\n if (msg.requestId !== requestId) return;\n\n cleanup();\n\n if (msg.type === 'BYOKY_CONNECT_RESPONSE') {\n resolve(msg.payload as ConnectResponse);\n } else if (msg.type === 'BYOKY_ERROR') {\n const { code, message } = msg.payload as {\n code: string;\n message: string;\n };\n reject(new ByokyError(code as ByokyErrorCode, message));\n }\n }\n\n function cleanup() {\n clearTimeout(timeoutId);\n document.removeEventListener('byoky-message', handleEvent);\n }\n\n document.addEventListener('byoky-message', handleEvent);\n\n window.postMessage(\n {\n type: 'BYOKY_CONNECT_REQUEST',\n id: requestId,\n requestId,\n payload: request,\n },\n '*',\n );\n });\n }\n\n private sendDisconnect(sessionKey: string): void {\n window.postMessage(\n { type: 'BYOKY_DISCONNECT', payload: { sessionKey } },\n '*',\n );\n }\n\n private querySessionStatus(sessionKey: string): Promise<boolean> {\n return new Promise((resolve) => {\n const requestId = crypto.randomUUID();\n const timeout = setTimeout(() => { cleanup(); resolve(false); }, 5000);\n\n function handleEvent(event: Event) {\n const msg = (event as CustomEvent).detail;\n if (msg?.requestId !== requestId) return;\n if (msg.type === 'BYOKY_SESSION_STATUS_RESPONSE') {\n cleanup();\n resolve(!!msg.payload?.connected);\n }\n }\n\n function cleanup() {\n clearTimeout(timeout);\n document.removeEventListener('byoky-message', handleEvent);\n }\n\n document.addEventListener('byoky-message', handleEvent);\n window.postMessage({\n type: 'BYOKY_SESSION_STATUS',\n requestId,\n payload: { sessionKey },\n }, '*');\n });\n }\n\n private querySessionUsage(sessionKey: string): Promise<SessionUsage> {\n return new Promise((resolve, reject) => {\n const requestId = crypto.randomUUID();\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error('Usage query timed out'));\n }, 5000);\n\n function handleEvent(event: Event) {\n const msg = (event as CustomEvent).detail;\n if (msg?.requestId !== requestId) return;\n if (msg.type === 'BYOKY_SESSION_USAGE_RESPONSE') {\n cleanup();\n if (msg.payload) {\n resolve(msg.payload as SessionUsage);\n } else {\n reject(new Error('Session not found'));\n }\n }\n }\n\n function cleanup() {\n clearTimeout(timeout);\n document.removeEventListener('byoky-message', handleEvent);\n }\n\n document.addEventListener('byoky-message', handleEvent);\n window.postMessage({\n type: 'BYOKY_SESSION_USAGE',\n requestId,\n payload: { sessionKey },\n }, '*');\n });\n }\n}\n","import { BYOKY_PROVIDER_KEY } from '@byoky/core';\n\nexport function isExtensionInstalled(): boolean {\n return typeof window !== 'undefined' && BYOKY_PROVIDER_KEY in window;\n}\n\nexport function getStoreUrl(): string | null {\n if (typeof navigator === 'undefined') return null;\n\n const ua = navigator.userAgent.toLowerCase();\n\n if (ua.includes('chrome') && !ua.includes('edg')) {\n return 'https://chrome.google.com/webstore/detail/byoky/TODO_EXTENSION_ID';\n }\n if (ua.includes('firefox')) {\n return 'https://addons.mozilla.org/en-US/firefox/addon/byoky/';\n }\n if (ua.includes('safari') && !ua.includes('chrome')) {\n return 'https://apps.apple.com/app/byoky/TODO_APP_ID';\n }\n return null;\n}\n","export function createProxyFetch(\n providerId: string,\n sessionKey: string,\n): typeof fetch {\n return async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.toString()\n : input.url;\n\n const method = init?.method ?? 'GET';\n const headers = init?.headers\n ? Object.fromEntries(new Headers(init.headers).entries())\n : {};\n const body = init?.body ? await readBody(init.body) : undefined;\n\n const requestId = crypto.randomUUID();\n\n return new Promise<Response>((resolve, reject) => {\n const { readable, writable } = new TransformStream<Uint8Array>();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n let resolved = false;\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error('Proxy request timed out'));\n }, 120_000);\n\n function handleEvent(event: Event) {\n const data = (event as CustomEvent).detail ?? (event as MessageEvent).data;\n if (data?.requestId !== requestId) return;\n\n switch (data.type) {\n case 'BYOKY_PROXY_RESPONSE_META':\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolve(\n new Response(readable, {\n status: data.status,\n statusText: data.statusText,\n headers: new Headers(data.headers),\n }),\n );\n }\n break;\n\n case 'BYOKY_PROXY_RESPONSE_CHUNK':\n writer.write(encoder.encode(data.chunk)).catch(() => {});\n break;\n\n case 'BYOKY_PROXY_RESPONSE_DONE':\n writer.close().catch(() => {});\n cleanup();\n break;\n\n case 'BYOKY_PROXY_RESPONSE_ERROR': {\n const errResponse = new Response(\n JSON.stringify({ error: data.error }),\n {\n status: data.status || 500,\n headers: { 'content-type': 'application/json' },\n },\n );\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolve(errResponse);\n }\n writer.close().catch(() => {});\n cleanup();\n break;\n }\n }\n }\n\n function cleanup() {\n clearTimeout(timeout);\n document.removeEventListener('byoky-message', handleEvent);\n }\n\n document.addEventListener('byoky-message', handleEvent);\n\n window.postMessage(\n {\n type: 'BYOKY_PROXY_REQUEST',\n requestId,\n sessionKey,\n providerId,\n url,\n method,\n headers,\n body,\n },\n '*',\n );\n });\n };\n}\n\nasync function readBody(body: BodyInit): Promise<string | undefined> {\n if (typeof body === 'string') return body;\n if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);\n if (body instanceof Blob) return body.text();\n if (body instanceof URLSearchParams) return body.toString();\n if (body instanceof ReadableStream) {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n const total = chunks.reduce((acc, c) => acc + c.length, 0);\n const combined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n return new TextDecoder().decode(combined);\n }\n return undefined;\n}\n","import type {\n AuthMethod,\n RelayMessage,\n RelayRequest,\n} from '@byoky/core';\nimport {\n ByokyError,\n parseRelayMessage,\n WS_READY_STATE,\n} from '@byoky/core';\nimport { createProxyFetch } from './proxy-fetch.js';\n\nexport interface RelayConnection {\n readonly status: 'connecting' | 'connected' | 'disconnected';\n close(): void;\n onClose(callback: (reason?: string) => void): () => void;\n}\n\nexport function createRelayClient(\n wsUrl: string,\n sessionKey: string,\n providers: Record<string, { available: boolean; authMethod: AuthMethod }>,\n): RelayConnection {\n let status: 'connecting' | 'connected' | 'disconnected' = 'connecting';\n const closeCallbacks = new Set<(reason?: string) => void>();\n const inFlight = new Map<string, AbortController>();\n\n let ws: WebSocket;\n let pingInterval: ReturnType<typeof setInterval> | undefined;\n\n try {\n const parsed = new URL(wsUrl);\n const isSecure = parsed.protocol === 'wss:';\n const isLocalWs = parsed.protocol === 'ws:' &&\n (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1');\n if (!isSecure && !isLocalWs) {\n status = 'disconnected';\n return {\n get status() { return status; },\n close() {},\n onClose(cb) { cb('Insecure WebSocket URL rejected — use wss:// for non-localhost connections'); return () => {}; },\n };\n }\n } catch {\n status = 'disconnected';\n return {\n get status() { return status; },\n close() {},\n onClose(cb) { cb('Invalid WebSocket URL'); return () => {}; },\n };\n }\n\n try {\n ws = new WebSocket(wsUrl);\n } catch {\n status = 'disconnected';\n return {\n get status() { return status; },\n close() {},\n onClose(cb) { cb('Failed to create WebSocket'); return () => {}; },\n };\n }\n\n ws.onopen = () => {\n status = 'connected';\n\n // Send hello with a relay-specific ID (never expose the real session key to the relay server)\n const relayId = `relay_${crypto.randomUUID().replace(/-/g, '')}`;\n ws.send(JSON.stringify({\n type: 'relay:hello',\n sessionId: relayId,\n providers,\n }));\n\n // Keepalive ping every 30s\n pingInterval = setInterval(() => {\n if (ws.readyState === WS_READY_STATE.OPEN) {\n ws.send(JSON.stringify({ type: 'relay:ping', ts: Date.now() }));\n }\n }, 30_000);\n };\n\n ws.onmessage = (event) => {\n const msg = parseRelayMessage(event.data);\n if (!msg) return;\n\n switch (msg.type) {\n case 'relay:request':\n handleRequest(msg);\n break;\n case 'relay:ping':\n ws.send(JSON.stringify({ type: 'relay:pong', ts: msg.ts }));\n break;\n case 'relay:pong':\n // Keepalive response, nothing to do\n break;\n }\n };\n\n ws.onclose = (event) => {\n cleanup(event.reason || 'WebSocket closed');\n };\n\n ws.onerror = () => {\n cleanup('WebSocket error');\n };\n\n async function handleRequest(req: RelayRequest) {\n const { requestId, providerId, url, method, headers, body } = req;\n const controller = new AbortController();\n inFlight.set(requestId, controller);\n\n try {\n const proxyFetch = createProxyFetch(providerId, sessionKey);\n const response = await proxyFetch(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n\n // Send response metadata\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n send({\n type: 'relay:response:meta',\n requestId,\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n\n // Stream the body\n if (response.body) {\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (controller.signal.aborted) break;\n\n send({\n type: 'relay:response:chunk',\n requestId,\n chunk: decoder.decode(value, { stream: true }),\n });\n }\n }\n\n send({ type: 'relay:response:done', requestId });\n } catch (err) {\n send({\n type: 'relay:response:error',\n requestId,\n error: {\n code: err instanceof ByokyError ? err.code : 'PROXY_ERROR',\n message: err instanceof Error ? err.message : 'Unknown error',\n },\n });\n } finally {\n inFlight.delete(requestId);\n }\n }\n\n function send(msg: RelayMessage) {\n if (ws.readyState === WS_READY_STATE.OPEN) {\n ws.send(JSON.stringify(msg));\n }\n }\n\n function cleanup(reason?: string) {\n if (status === 'disconnected') return;\n status = 'disconnected';\n\n if (pingInterval) clearInterval(pingInterval);\n\n // Abort all in-flight requests\n for (const controller of inFlight.values()) {\n controller.abort();\n }\n inFlight.clear();\n\n for (const cb of closeCallbacks) cb(reason);\n closeCallbacks.clear();\n }\n\n return {\n get status() { return status; },\n close() {\n if (ws.readyState === WS_READY_STATE.OPEN || ws.readyState === WS_READY_STATE.CONNECTING) {\n ws.close(1000, 'Client closed');\n }\n cleanup('Client closed');\n },\n onClose(callback: (reason?: string) => void) {\n closeCallbacks.add(callback);\n return () => { closeCallbacks.delete(callback); };\n },\n };\n}\n","export { Byoky } from './byoky.js';\nexport type { ByokySession, ByokyOptions } from './byoky.js';\nexport { isExtensionInstalled, getStoreUrl } from './detect.js';\nexport { createProxyFetch } from './proxy-fetch.js';\nexport type { RelayConnection } from './relay-client.js';\nexport {\n type ConnectRequest,\n type ConnectResponse,\n type ProviderRequirement,\n type SessionUsage,\n ByokyError,\n ByokyErrorCode,\n} from '@byoky/core';\n"],"mappings":"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","names":["ByokyError","ByokyError","ByokyError","ByokyErrorCode"]}
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
ByokyServer: () => ByokyServer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(server_exports);
|
|
26
|
+
var import_core = require("@byoky/core");
|
|
27
|
+
var ByokyServer = class {
|
|
28
|
+
pingInterval;
|
|
29
|
+
helloTimeout;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.pingInterval = options.pingInterval ?? 3e4;
|
|
32
|
+
this.helloTimeout = options.helloTimeout ?? 1e4;
|
|
33
|
+
}
|
|
34
|
+
handleConnection(ws) {
|
|
35
|
+
const self = this;
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
let sessionId = "";
|
|
38
|
+
let providers = {};
|
|
39
|
+
let connected = false;
|
|
40
|
+
const closeCallbacks = /* @__PURE__ */ new Set();
|
|
41
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
42
|
+
let pingTimer;
|
|
43
|
+
let requestCounter = 0;
|
|
44
|
+
const helloTimer = setTimeout(() => {
|
|
45
|
+
reject(import_core.ByokyError.relayConnectionFailed("Timed out waiting for relay:hello"));
|
|
46
|
+
ws.close(4e3, "Hello timeout");
|
|
47
|
+
}, this.helloTimeout);
|
|
48
|
+
ws.onmessage = (event) => {
|
|
49
|
+
const msg = (0, import_core.parseRelayMessage)(event.data);
|
|
50
|
+
if (!msg) return;
|
|
51
|
+
switch (msg.type) {
|
|
52
|
+
case "relay:hello":
|
|
53
|
+
handleHello(msg);
|
|
54
|
+
break;
|
|
55
|
+
case "relay:response:meta": {
|
|
56
|
+
const pending = pendingRequests.get(msg.requestId);
|
|
57
|
+
if (pending) {
|
|
58
|
+
pending.resolveMeta({
|
|
59
|
+
status: msg.status,
|
|
60
|
+
statusText: msg.statusText,
|
|
61
|
+
headers: msg.headers
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "relay:response:chunk": {
|
|
67
|
+
const pending = pendingRequests.get(msg.requestId);
|
|
68
|
+
if (pending) pending.pushChunk(msg.chunk);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "relay:response:done": {
|
|
72
|
+
const pending = pendingRequests.get(msg.requestId);
|
|
73
|
+
if (pending) {
|
|
74
|
+
pending.done();
|
|
75
|
+
pendingRequests.delete(msg.requestId);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "relay:response:error": {
|
|
80
|
+
const pending = pendingRequests.get(msg.requestId);
|
|
81
|
+
if (pending) {
|
|
82
|
+
pending.error(new Error(msg.error.message));
|
|
83
|
+
pendingRequests.delete(msg.requestId);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "relay:ping":
|
|
88
|
+
(0, import_core.sendRelayMessage)(ws, { type: "relay:pong", ts: msg.ts });
|
|
89
|
+
break;
|
|
90
|
+
case "relay:pong":
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
ws.onclose = () => {
|
|
95
|
+
cleanup();
|
|
96
|
+
};
|
|
97
|
+
ws.onerror = () => {
|
|
98
|
+
cleanup();
|
|
99
|
+
};
|
|
100
|
+
function handleHello(msg) {
|
|
101
|
+
clearTimeout(helloTimer);
|
|
102
|
+
sessionId = msg.sessionId;
|
|
103
|
+
providers = msg.providers;
|
|
104
|
+
connected = true;
|
|
105
|
+
if (self.pingInterval > 0) {
|
|
106
|
+
pingTimer = setInterval(() => {
|
|
107
|
+
(0, import_core.sendRelayMessage)(ws, { type: "relay:ping", ts: Date.now() });
|
|
108
|
+
}, self.pingInterval);
|
|
109
|
+
}
|
|
110
|
+
resolve(client);
|
|
111
|
+
}
|
|
112
|
+
function cleanup() {
|
|
113
|
+
if (!connected) return;
|
|
114
|
+
connected = false;
|
|
115
|
+
clearTimeout(helloTimer);
|
|
116
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
117
|
+
for (const pending of pendingRequests.values()) {
|
|
118
|
+
pending.error(import_core.ByokyError.relayDisconnected());
|
|
119
|
+
}
|
|
120
|
+
pendingRequests.clear();
|
|
121
|
+
for (const cb of closeCallbacks) cb();
|
|
122
|
+
closeCallbacks.clear();
|
|
123
|
+
}
|
|
124
|
+
function createClientFetch(providerId) {
|
|
125
|
+
return async (input, init) => {
|
|
126
|
+
if (!connected) throw import_core.ByokyError.relayDisconnected();
|
|
127
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
128
|
+
const method = init?.method ?? "GET";
|
|
129
|
+
const headers = init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : {};
|
|
130
|
+
const body = init?.body ? await readBody(init.body) : void 0;
|
|
131
|
+
const requestId = `relay-${++requestCounter}-${Date.now()}`;
|
|
132
|
+
return new Promise((resolveFetch, rejectFetch) => {
|
|
133
|
+
let metaResolved = false;
|
|
134
|
+
let controller;
|
|
135
|
+
const encoder = new TextEncoder();
|
|
136
|
+
const stream = new ReadableStream({
|
|
137
|
+
start(c) {
|
|
138
|
+
controller = c;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
pendingRequests.set(requestId, {
|
|
142
|
+
resolveMeta: (meta) => {
|
|
143
|
+
if (metaResolved) return;
|
|
144
|
+
metaResolved = true;
|
|
145
|
+
resolveFetch(new Response(stream, {
|
|
146
|
+
status: meta.status,
|
|
147
|
+
statusText: meta.statusText,
|
|
148
|
+
headers: new Headers(meta.headers)
|
|
149
|
+
}));
|
|
150
|
+
},
|
|
151
|
+
pushChunk: (chunk) => {
|
|
152
|
+
try {
|
|
153
|
+
controller.enqueue(encoder.encode(chunk));
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
done: () => {
|
|
158
|
+
try {
|
|
159
|
+
controller.close();
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
error: (err) => {
|
|
164
|
+
try {
|
|
165
|
+
controller.error(err);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
if (!metaResolved) {
|
|
169
|
+
metaResolved = true;
|
|
170
|
+
rejectFetch(err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
(0, import_core.sendRelayMessage)(ws, {
|
|
175
|
+
type: "relay:request",
|
|
176
|
+
requestId,
|
|
177
|
+
providerId,
|
|
178
|
+
url,
|
|
179
|
+
method,
|
|
180
|
+
headers,
|
|
181
|
+
body
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const client = {
|
|
187
|
+
get sessionId() {
|
|
188
|
+
return sessionId;
|
|
189
|
+
},
|
|
190
|
+
get providers() {
|
|
191
|
+
return providers;
|
|
192
|
+
},
|
|
193
|
+
get connected() {
|
|
194
|
+
return connected;
|
|
195
|
+
},
|
|
196
|
+
createFetch: (providerId) => createClientFetch(providerId),
|
|
197
|
+
close() {
|
|
198
|
+
ws.close(1e3, "Server closed");
|
|
199
|
+
cleanup();
|
|
200
|
+
},
|
|
201
|
+
onClose(callback) {
|
|
202
|
+
closeCallbacks.add(callback);
|
|
203
|
+
return () => {
|
|
204
|
+
closeCallbacks.delete(callback);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
async function readBody(body) {
|
|
212
|
+
if (typeof body === "string") return body;
|
|
213
|
+
if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|
|
214
|
+
if (body instanceof Uint8Array) return new TextDecoder().decode(body);
|
|
215
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return body.text();
|
|
216
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) return body.toString();
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
220
|
+
0 && (module.exports = {
|
|
221
|
+
ByokyServer
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=server.cjs.map
|