@standardserver/peer 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +49 -11
- package/dist/index.d.ts +49 -11
- package/dist/index.mjs +91 -77
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -202,22 +202,60 @@ declare class ClientPeer {
|
|
|
202
202
|
close(options?: AsyncIdQueueCloseOptions): Promise<void>;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
interface EncodePeerMessageOptions {
|
|
206
|
+
/**
|
|
207
|
+
* Optional string prepended to the encoded message.
|
|
208
|
+
* Used to distinguish messages when multiple protocols share the same peer.
|
|
209
|
+
*/
|
|
210
|
+
prefix?: string;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Encodes a {@link PeerMessage} into a wire-safe representation.
|
|
214
|
+
*
|
|
215
|
+
* Encoding rules:
|
|
216
|
+
* - If no binary payload is present, the message is encoded as a JSON string.
|
|
217
|
+
* - If binary data exists, the output layout is:
|
|
218
|
+
*
|
|
219
|
+
* [ UTF-8 (prefix + JSON) | delimiter byte | raw binary bytes ]
|
|
220
|
+
*
|
|
221
|
+
* The optional prefix is prepended to the JSON portion before encoding.
|
|
222
|
+
*/
|
|
223
|
+
declare function encodePeerMessage(message: PeerMessage, options?: EncodePeerMessageOptions): Promise<string | Uint8Array<ArrayBuffer>>;
|
|
224
|
+
interface DecodePeerMessageOptions {
|
|
225
|
+
/**
|
|
226
|
+
* Optional prefix expected at the start of the encoded message.
|
|
227
|
+
* If present and the message does not start with this prefix,
|
|
228
|
+
* the decoder returns `matched: false`.
|
|
229
|
+
*/
|
|
230
|
+
prefix?: string;
|
|
231
|
+
}
|
|
205
232
|
/**
|
|
206
|
-
*
|
|
233
|
+
* Result of a decode attempt.
|
|
207
234
|
*
|
|
208
|
-
* -
|
|
209
|
-
*
|
|
210
|
-
*
|
|
235
|
+
* - `matched: false` indicates the input does not belong to this decoder
|
|
236
|
+
* (typically due to a prefix mismatch).
|
|
237
|
+
* - `matched: true` indicates successful decoding of a {@link PeerMessage}.
|
|
211
238
|
*/
|
|
212
|
-
|
|
239
|
+
type DecodePeerMessageResult = {
|
|
240
|
+
matched: false;
|
|
241
|
+
message?: undefined;
|
|
242
|
+
} | {
|
|
243
|
+
matched: true;
|
|
244
|
+
message: PeerMessage;
|
|
245
|
+
};
|
|
213
246
|
/**
|
|
214
|
-
* Decodes a wire-encoded PeerMessage.
|
|
247
|
+
* Decodes a wire-encoded {@link PeerMessage}.
|
|
248
|
+
*
|
|
249
|
+
* Decoding rules:
|
|
250
|
+
* - String input is treated as a JSON-only message.
|
|
251
|
+
* - Binary input may contain:
|
|
252
|
+
* - JSON only, or
|
|
253
|
+
* - JSON followed by binary data separated by the delimiter byte.
|
|
215
254
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* separated by the separator byte.
|
|
255
|
+
* If a prefix is provided, it must be present at the start of the payload
|
|
256
|
+
* or the decode attempt will return `matched: false`.
|
|
219
257
|
*/
|
|
220
|
-
declare function decodePeerMessage(
|
|
258
|
+
declare function decodePeerMessage(encoded: string | Uint8Array<ArrayBuffer>, options?: DecodePeerMessageOptions): DecodePeerMessageResult;
|
|
221
259
|
|
|
222
260
|
/**
|
|
223
261
|
* Creates an AsyncIterator from a queue of peer event-stream messages.
|
|
@@ -277,4 +315,4 @@ declare class ServerPeer {
|
|
|
277
315
|
}
|
|
278
316
|
|
|
279
317
|
export { ClientPeer, EventStreamTransmitter, HibernationEventIterator, ServerPeer, decodePeerMessage, encodePeerMessage, toEventIterator };
|
|
280
|
-
export type { ClientPeerCloseOptions, HibernationEventIteratorCallback, PeerAbortMessage, PeerEventStreamMessage, PeerMessage, PeerOctetStreamMessage, PeerRequestMessage, PeerResponseMessage, PeerStreamCancelMessage, ServerPeerCloseOptions };
|
|
318
|
+
export type { ClientPeerCloseOptions, DecodePeerMessageOptions, DecodePeerMessageResult, EncodePeerMessageOptions, HibernationEventIteratorCallback, PeerAbortMessage, PeerEventStreamMessage, PeerMessage, PeerOctetStreamMessage, PeerRequestMessage, PeerResponseMessage, PeerStreamCancelMessage, ServerPeerCloseOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -202,22 +202,60 @@ declare class ClientPeer {
|
|
|
202
202
|
close(options?: AsyncIdQueueCloseOptions): Promise<void>;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
interface EncodePeerMessageOptions {
|
|
206
|
+
/**
|
|
207
|
+
* Optional string prepended to the encoded message.
|
|
208
|
+
* Used to distinguish messages when multiple protocols share the same peer.
|
|
209
|
+
*/
|
|
210
|
+
prefix?: string;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Encodes a {@link PeerMessage} into a wire-safe representation.
|
|
214
|
+
*
|
|
215
|
+
* Encoding rules:
|
|
216
|
+
* - If no binary payload is present, the message is encoded as a JSON string.
|
|
217
|
+
* - If binary data exists, the output layout is:
|
|
218
|
+
*
|
|
219
|
+
* [ UTF-8 (prefix + JSON) | delimiter byte | raw binary bytes ]
|
|
220
|
+
*
|
|
221
|
+
* The optional prefix is prepended to the JSON portion before encoding.
|
|
222
|
+
*/
|
|
223
|
+
declare function encodePeerMessage(message: PeerMessage, options?: EncodePeerMessageOptions): Promise<string | Uint8Array<ArrayBuffer>>;
|
|
224
|
+
interface DecodePeerMessageOptions {
|
|
225
|
+
/**
|
|
226
|
+
* Optional prefix expected at the start of the encoded message.
|
|
227
|
+
* If present and the message does not start with this prefix,
|
|
228
|
+
* the decoder returns `matched: false`.
|
|
229
|
+
*/
|
|
230
|
+
prefix?: string;
|
|
231
|
+
}
|
|
205
232
|
/**
|
|
206
|
-
*
|
|
233
|
+
* Result of a decode attempt.
|
|
207
234
|
*
|
|
208
|
-
* -
|
|
209
|
-
*
|
|
210
|
-
*
|
|
235
|
+
* - `matched: false` indicates the input does not belong to this decoder
|
|
236
|
+
* (typically due to a prefix mismatch).
|
|
237
|
+
* - `matched: true` indicates successful decoding of a {@link PeerMessage}.
|
|
211
238
|
*/
|
|
212
|
-
|
|
239
|
+
type DecodePeerMessageResult = {
|
|
240
|
+
matched: false;
|
|
241
|
+
message?: undefined;
|
|
242
|
+
} | {
|
|
243
|
+
matched: true;
|
|
244
|
+
message: PeerMessage;
|
|
245
|
+
};
|
|
213
246
|
/**
|
|
214
|
-
* Decodes a wire-encoded PeerMessage.
|
|
247
|
+
* Decodes a wire-encoded {@link PeerMessage}.
|
|
248
|
+
*
|
|
249
|
+
* Decoding rules:
|
|
250
|
+
* - String input is treated as a JSON-only message.
|
|
251
|
+
* - Binary input may contain:
|
|
252
|
+
* - JSON only, or
|
|
253
|
+
* - JSON followed by binary data separated by the delimiter byte.
|
|
215
254
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* separated by the separator byte.
|
|
255
|
+
* If a prefix is provided, it must be present at the start of the payload
|
|
256
|
+
* or the decode attempt will return `matched: false`.
|
|
219
257
|
*/
|
|
220
|
-
declare function decodePeerMessage(
|
|
258
|
+
declare function decodePeerMessage(encoded: string | Uint8Array<ArrayBuffer>, options?: DecodePeerMessageOptions): DecodePeerMessageResult;
|
|
221
259
|
|
|
222
260
|
/**
|
|
223
261
|
* Creates an AsyncIterator from a queue of peer event-stream messages.
|
|
@@ -277,4 +315,4 @@ declare class ServerPeer {
|
|
|
277
315
|
}
|
|
278
316
|
|
|
279
317
|
export { ClientPeer, EventStreamTransmitter, HibernationEventIterator, ServerPeer, decodePeerMessage, encodePeerMessage, toEventIterator };
|
|
280
|
-
export type { ClientPeerCloseOptions, HibernationEventIteratorCallback, PeerAbortMessage, PeerEventStreamMessage, PeerMessage, PeerOctetStreamMessage, PeerRequestMessage, PeerResponseMessage, PeerStreamCancelMessage, ServerPeerCloseOptions };
|
|
318
|
+
export type { ClientPeerCloseOptions, DecodePeerMessageOptions, DecodePeerMessageResult, EncodePeerMessageOptions, HibernationEventIteratorCallback, PeerAbortMessage, PeerEventStreamMessage, PeerMessage, PeerOctetStreamMessage, PeerRequestMessage, PeerResponseMessage, PeerStreamCancelMessage, ServerPeerCloseOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { AsyncIteratorClass, isTypescriptObject, isAsyncIteratorObject, SequentialIdGenerator, AsyncIdQueue, AbortError, stringifyJSON } from '@standardserver/shared';
|
|
2
|
+
import { generateContentDisposition, flattenStandardHeader, getFilenameFromContentDisposition } from '@standardserver/core';
|
|
3
3
|
import { withEventIteratorEventMeta, EventIteratorErrorEvent, resolveEventIteratorEvent } from '@standardserver/core/event-stream';
|
|
4
4
|
|
|
5
5
|
function toEventIterator(pull, cleanup) {
|
|
@@ -181,6 +181,37 @@ async function toStandardBody(message, eventStreamMessageQueue, octetStreamMessa
|
|
|
181
181
|
await cleanup(true);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
async function encodeAtomicStandardBody(body, headers) {
|
|
185
|
+
headers = { ...headers };
|
|
186
|
+
let binary;
|
|
187
|
+
let jsonBody = body;
|
|
188
|
+
headers["standard-server"] = void 0;
|
|
189
|
+
if (body instanceof ReadableStream) {
|
|
190
|
+
jsonBody = void 0;
|
|
191
|
+
headers["standard-server"] = "octet-stream";
|
|
192
|
+
} else if (isAsyncIteratorObject(body)) {
|
|
193
|
+
jsonBody = void 0;
|
|
194
|
+
headers["standard-server"] = "event-stream";
|
|
195
|
+
} else if (body instanceof FormData) {
|
|
196
|
+
const res = new Response(body);
|
|
197
|
+
binary = await res.blob();
|
|
198
|
+
jsonBody = void 0;
|
|
199
|
+
headers["standard-server"] = "form-data";
|
|
200
|
+
headers["content-type"] ??= res.headers.get("content-type");
|
|
201
|
+
} else if (body instanceof Blob) {
|
|
202
|
+
binary = body;
|
|
203
|
+
jsonBody = void 0;
|
|
204
|
+
headers["standard-server"] = "file";
|
|
205
|
+
headers["content-disposition"] ??= generateContentDisposition(
|
|
206
|
+
body instanceof File ? body.name : "blob"
|
|
207
|
+
);
|
|
208
|
+
headers["content-type"] ??= body.type;
|
|
209
|
+
} else if (body instanceof URLSearchParams) {
|
|
210
|
+
jsonBody = body.toString();
|
|
211
|
+
headers["standard-server"] = "url-search-params";
|
|
212
|
+
}
|
|
213
|
+
return [jsonBody, headers, binary];
|
|
214
|
+
}
|
|
184
215
|
|
|
185
216
|
class ClientPeer {
|
|
186
217
|
constructor(send) {
|
|
@@ -245,39 +276,20 @@ class ClientPeer {
|
|
|
245
276
|
];
|
|
246
277
|
this.cleanupFns.set(id, cleanupFns);
|
|
247
278
|
try {
|
|
279
|
+
const [jsonBody, headers, binary] = await encodeAtomicStandardBody(request.body, request.headers);
|
|
280
|
+
signal?.throwIfAborted();
|
|
248
281
|
const requestMessage = {
|
|
249
282
|
id,
|
|
250
283
|
kind: "request",
|
|
251
284
|
json: {
|
|
252
|
-
...
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
285
|
+
...request,
|
|
286
|
+
headers,
|
|
287
|
+
body: jsonBody,
|
|
288
|
+
...{ signal: void 0 }
|
|
289
|
+
// remove signal from request
|
|
290
|
+
},
|
|
291
|
+
binary
|
|
257
292
|
};
|
|
258
|
-
if (request.body instanceof ReadableStream) {
|
|
259
|
-
requestMessage.json.body = void 0;
|
|
260
|
-
requestMessage.json.headers["standard-server"] = "octet-stream";
|
|
261
|
-
} else if (isAsyncIteratorObject(request.body)) {
|
|
262
|
-
requestMessage.json.body = void 0;
|
|
263
|
-
requestMessage.json.headers["standard-server"] = "event-stream";
|
|
264
|
-
} else if (request.body instanceof FormData) {
|
|
265
|
-
const res = new Response(request.body);
|
|
266
|
-
requestMessage.binary = await res.blob();
|
|
267
|
-
requestMessage.json.body = void 0;
|
|
268
|
-
requestMessage.json.headers["standard-server"] = "form-data";
|
|
269
|
-
requestMessage.json.headers["content-type"] = res.headers.get("content-type") ?? void 0;
|
|
270
|
-
} else if (request.body instanceof Blob) {
|
|
271
|
-
requestMessage.binary = request.body;
|
|
272
|
-
requestMessage.json.body = void 0;
|
|
273
|
-
requestMessage.json.headers["standard-server"] = "file";
|
|
274
|
-
requestMessage.json.headers["content-disposition"] = generateContentDisposition(request.body instanceof File ? request.body.name : "blob");
|
|
275
|
-
requestMessage.json.headers["content-type"] = request.body.type;
|
|
276
|
-
} else if (request.body instanceof URLSearchParams) {
|
|
277
|
-
requestMessage.json.body = request.body.toString();
|
|
278
|
-
requestMessage.json.headers["standard-server"] = "url-search-params";
|
|
279
|
-
}
|
|
280
|
-
signal?.throwIfAborted();
|
|
281
293
|
await this.send(requestMessage);
|
|
282
294
|
signal?.throwIfAborted();
|
|
283
295
|
if (isAsyncIteratorObject(request.body)) {
|
|
@@ -391,36 +403,57 @@ class ClientPeer {
|
|
|
391
403
|
}
|
|
392
404
|
}
|
|
393
405
|
|
|
394
|
-
const
|
|
406
|
+
const JSON_BINARY_DELIMITER = 255;
|
|
395
407
|
const textEncoder = new TextEncoder();
|
|
396
408
|
const textDecoder = new TextDecoder();
|
|
397
|
-
async function encodePeerMessage(message) {
|
|
409
|
+
async function encodePeerMessage(message, options = {}) {
|
|
410
|
+
const jsonPart = stringifyJSON({ ...message, binary: void 0 });
|
|
398
411
|
if (message.binary === void 0) {
|
|
399
|
-
return
|
|
412
|
+
return options.prefix ? options.prefix + jsonPart : jsonPart;
|
|
400
413
|
}
|
|
401
|
-
const
|
|
414
|
+
const textBytes = textEncoder.encode(
|
|
415
|
+
options.prefix ? options.prefix + jsonPart : jsonPart
|
|
416
|
+
);
|
|
402
417
|
const binaryBytes = message.binary instanceof Blob ? new Uint8Array(await message.binary.arrayBuffer()) : message.binary;
|
|
403
418
|
const output = new Uint8Array(
|
|
404
|
-
|
|
419
|
+
textBytes.length + 1 + binaryBytes.length
|
|
405
420
|
);
|
|
406
|
-
output.set(
|
|
407
|
-
output[
|
|
408
|
-
output.set(binaryBytes,
|
|
421
|
+
output.set(textBytes, 0);
|
|
422
|
+
output[textBytes.length] = JSON_BINARY_DELIMITER;
|
|
423
|
+
output.set(binaryBytes, textBytes.length + 1);
|
|
409
424
|
return output;
|
|
410
425
|
}
|
|
411
|
-
function decodePeerMessage(
|
|
412
|
-
if (typeof
|
|
413
|
-
|
|
426
|
+
function decodePeerMessage(encoded, options = {}) {
|
|
427
|
+
if (typeof encoded === "string") {
|
|
428
|
+
if (options.prefix) {
|
|
429
|
+
if (!encoded.startsWith(options.prefix)) {
|
|
430
|
+
return { matched: false };
|
|
431
|
+
}
|
|
432
|
+
encoded = encoded.slice(options.prefix.length);
|
|
433
|
+
}
|
|
434
|
+
return { matched: true, message: JSON.parse(encoded) };
|
|
435
|
+
}
|
|
436
|
+
if (options.prefix) {
|
|
437
|
+
const prefixBytes = textEncoder.encode(options.prefix);
|
|
438
|
+
for (let i = 0; i < prefixBytes.length; i++) {
|
|
439
|
+
if (encoded[i] !== prefixBytes[i]) {
|
|
440
|
+
return { matched: false };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
encoded = encoded.subarray(prefixBytes.length);
|
|
414
444
|
}
|
|
415
|
-
const separatorIndex =
|
|
445
|
+
const separatorIndex = encoded.indexOf(JSON_BINARY_DELIMITER);
|
|
416
446
|
if (separatorIndex === -1) {
|
|
417
|
-
return JSON.parse(textDecoder.decode(
|
|
447
|
+
return { matched: true, message: JSON.parse(textDecoder.decode(encoded)) };
|
|
418
448
|
}
|
|
419
|
-
const jsonBytes =
|
|
420
|
-
const binaryBytes =
|
|
449
|
+
const jsonBytes = encoded.subarray(0, separatorIndex);
|
|
450
|
+
const binaryBytes = encoded.subarray(separatorIndex + 1);
|
|
421
451
|
return {
|
|
422
|
-
|
|
423
|
-
|
|
452
|
+
matched: true,
|
|
453
|
+
message: {
|
|
454
|
+
...JSON.parse(textDecoder.decode(jsonBytes)),
|
|
455
|
+
binary: binaryBytes
|
|
456
|
+
}
|
|
424
457
|
};
|
|
425
458
|
}
|
|
426
459
|
|
|
@@ -509,41 +542,22 @@ class ServerPeer {
|
|
|
509
542
|
if (signal.aborted) {
|
|
510
543
|
return;
|
|
511
544
|
}
|
|
545
|
+
const [jsonBody, headers, binary] = await encodeAtomicStandardBody(response.body, response.headers);
|
|
546
|
+
if (signal.aborted) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
512
549
|
const responseMessage = {
|
|
513
550
|
id: message.id,
|
|
514
551
|
kind: "response",
|
|
515
552
|
json: {
|
|
516
|
-
...
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
553
|
+
...response,
|
|
554
|
+
headers,
|
|
555
|
+
body: jsonBody,
|
|
556
|
+
...{ signal: void 0 }
|
|
557
|
+
// remove signal from request
|
|
558
|
+
},
|
|
559
|
+
binary
|
|
521
560
|
};
|
|
522
|
-
if (response.body instanceof ReadableStream) {
|
|
523
|
-
responseMessage.json.body = void 0;
|
|
524
|
-
responseMessage.json.headers["standard-server"] = "octet-stream";
|
|
525
|
-
} else if (isAsyncIteratorObject(response.body)) {
|
|
526
|
-
responseMessage.json.body = void 0;
|
|
527
|
-
responseMessage.json.headers["standard-server"] = "event-stream";
|
|
528
|
-
} else if (response.body instanceof FormData) {
|
|
529
|
-
const res = new Response(response.body);
|
|
530
|
-
responseMessage.binary = await res.blob();
|
|
531
|
-
responseMessage.json.body = void 0;
|
|
532
|
-
responseMessage.json.headers["standard-server"] = "form-data";
|
|
533
|
-
responseMessage.json.headers["content-type"] = res.headers.get("content-type") ?? void 0;
|
|
534
|
-
} else if (response.body instanceof Blob) {
|
|
535
|
-
responseMessage.binary = response.body;
|
|
536
|
-
responseMessage.json.body = void 0;
|
|
537
|
-
responseMessage.json.headers["standard-server"] = "file";
|
|
538
|
-
responseMessage.json.headers["content-disposition"] = generateContentDisposition(response.body instanceof File ? response.body.name : "blob");
|
|
539
|
-
responseMessage.json.headers["content-type"] = response.body.type;
|
|
540
|
-
} else if (response.body instanceof URLSearchParams) {
|
|
541
|
-
responseMessage.json.body = response.body.toString();
|
|
542
|
-
responseMessage.json.headers["standard-server"] = "url-search-params";
|
|
543
|
-
}
|
|
544
|
-
if (signal.aborted) {
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
561
|
await this.send(responseMessage);
|
|
548
562
|
if (signal.aborted) {
|
|
549
563
|
return;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@standardserver/peer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://standardserver.dev",
|
|
7
7
|
"repository": {
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@standardserver/core": "0.0.
|
|
24
|
-
"@standardserver/shared": "0.0.
|
|
23
|
+
"@standardserver/core": "0.0.2",
|
|
24
|
+
"@standardserver/shared": "0.0.2"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "unbuild",
|