@standardserver/peer 0.0.7 → 0.0.8
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 +38 -46
- package/dist/index.d.ts +38 -46
- package/dist/index.mjs +362 -294
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { StandardRequest, StandardResponse } from '@standardserver/core';
|
|
2
|
-
import { AsyncIdQueueCloseOptions, AsyncCleanupFn, AsyncIteratorClass } from '@standardserver/shared';
|
|
1
|
+
import { StandardRequest, StandardResponse, StandardLazyResponse, StandardLazyRequest } from '@standardserver/core';
|
|
3
2
|
import { EventStreamMessage } from '@standardserver/core/event-stream';
|
|
3
|
+
import { Queue, AsyncCleanupFn, AsyncIteratorClass } from '@standardserver/shared';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Base interface for all peer messages.
|
|
@@ -68,17 +68,17 @@ interface PeerResponseMessage extends PeerMessage {
|
|
|
68
68
|
* - **Client → Server**: Cancel an in-flight request or stop consuming a stream.
|
|
69
69
|
* - **Server → Client**: Signal an error or premature termination.
|
|
70
70
|
*/
|
|
71
|
-
interface
|
|
71
|
+
interface PeerCancelMessage extends PeerMessage {
|
|
72
72
|
/**
|
|
73
73
|
* The kind of the message.
|
|
74
74
|
*/
|
|
75
|
-
kind: '
|
|
75
|
+
kind: 'cancel';
|
|
76
76
|
/**
|
|
77
77
|
* This message does not have a JSON payload.
|
|
78
78
|
*/
|
|
79
79
|
json?: undefined;
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* Cancel messages carry no binary payload.
|
|
82
82
|
*/
|
|
83
83
|
binary?: undefined;
|
|
84
84
|
}
|
|
@@ -164,29 +164,20 @@ interface PeerStreamCancelMessage extends PeerMessage {
|
|
|
164
164
|
*/
|
|
165
165
|
binary?: undefined;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Union of all messages a client peer may send to a server peer.
|
|
169
|
+
*/
|
|
170
|
+
type ClientPeerSendMessage = PeerRequestMessage | PeerCancelMessage | PeerEventStreamMessage | PeerOctetStreamMessage;
|
|
171
|
+
/**
|
|
172
|
+
* Union of all messages a server peer may send to a client peer.
|
|
173
|
+
*/
|
|
174
|
+
type ServerPeerSendMessage = PeerResponseMessage | PeerCancelMessage | PeerOctetStreamMessage | PeerEventStreamMessage | PeerStreamCancelMessage;
|
|
167
175
|
|
|
168
|
-
interface ClientPeerCloseOptions extends AsyncIdQueueCloseOptions {
|
|
169
|
-
}
|
|
170
176
|
declare class ClientPeer {
|
|
171
177
|
private readonly send;
|
|
172
178
|
private readonly idGenerator;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*/
|
|
176
|
-
private readonly responseMessageQueue;
|
|
177
|
-
private readonly eventStreamMessageQueue;
|
|
178
|
-
private readonly octetStreamMessageQueue;
|
|
179
|
-
/**
|
|
180
|
-
* Transmitters for event streams and octet streams
|
|
181
|
-
* Should be cancelled when needed
|
|
182
|
-
*/
|
|
183
|
-
private readonly requestEventStreamTransmitters;
|
|
184
|
-
private readonly requestOctetStreamTransmitters;
|
|
185
|
-
/**
|
|
186
|
-
* Cleanup functions invoked when the request/response is completed
|
|
187
|
-
*/
|
|
188
|
-
private readonly cleanupFns;
|
|
189
|
-
constructor(send: (message: PeerAbortMessage | PeerRequestMessage | PeerEventStreamMessage | PeerOctetStreamMessage) => Promise<void>);
|
|
179
|
+
private readonly requests;
|
|
180
|
+
constructor(send: (message: ClientPeerSendMessage) => Promise<void>);
|
|
190
181
|
/**
|
|
191
182
|
* Use to measure resources usage
|
|
192
183
|
*/
|
|
@@ -194,12 +185,14 @@ declare class ClientPeer {
|
|
|
194
185
|
/**
|
|
195
186
|
* Send a request to the server peer
|
|
196
187
|
*/
|
|
197
|
-
request(request: StandardRequest): Promise<
|
|
188
|
+
request(request: StandardRequest): Promise<StandardLazyResponse>;
|
|
198
189
|
/**
|
|
199
190
|
* Handle a message from server
|
|
200
191
|
*/
|
|
201
|
-
message(message:
|
|
202
|
-
close(
|
|
192
|
+
message(message: ServerPeerSendMessage): Promise<void>;
|
|
193
|
+
close(reason?: unknown): Promise<void>;
|
|
194
|
+
private closeById;
|
|
195
|
+
private abortById;
|
|
203
196
|
}
|
|
204
197
|
|
|
205
198
|
interface EncodePeerMessageOptions {
|
|
@@ -261,7 +254,7 @@ declare function decodePeerMessage(encoded: string | Uint8Array<ArrayBuffer>, op
|
|
|
261
254
|
* Creates an AsyncIterator from a queue of peer event-stream messages.
|
|
262
255
|
* The iterator yields normal events, throws error events, and completes on done.
|
|
263
256
|
*/
|
|
264
|
-
declare function toEventIterator(
|
|
257
|
+
declare function toEventIterator(queue: Queue<PeerEventStreamMessage>, cleanup: AsyncCleanupFn): AsyncIteratorClass<unknown>;
|
|
265
258
|
/**
|
|
266
259
|
* Transmits events to a peer event-stream.
|
|
267
260
|
*/
|
|
@@ -287,22 +280,10 @@ declare class HibernationEventIterator<T, TReturn = unknown, TNext = unknown> ex
|
|
|
287
280
|
constructor(hibernationCallback: HibernationEventIteratorCallback);
|
|
288
281
|
}
|
|
289
282
|
|
|
290
|
-
interface ServerPeerCloseOptions extends AsyncIdQueueCloseOptions {
|
|
291
|
-
}
|
|
292
283
|
declare class ServerPeer {
|
|
293
284
|
private readonly send;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
*/
|
|
297
|
-
private readonly eventStreamMessageQueue;
|
|
298
|
-
private readonly octetStreamMessageQueue;
|
|
299
|
-
private readonly eventStreamTransmitters;
|
|
300
|
-
private readonly octetStreamTransmitters;
|
|
301
|
-
/**
|
|
302
|
-
* Map of abort controllers for each request
|
|
303
|
-
*/
|
|
304
|
-
private readonly controller;
|
|
305
|
-
constructor(send: (message: PeerResponseMessage | PeerAbortMessage | PeerOctetStreamMessage | PeerEventStreamMessage | PeerStreamCancelMessage) => Promise<void>);
|
|
285
|
+
private readonly requests;
|
|
286
|
+
constructor(send: (message: ServerPeerSendMessage) => Promise<void>);
|
|
306
287
|
/**
|
|
307
288
|
* Use for measure resources usage
|
|
308
289
|
*/
|
|
@@ -310,9 +291,20 @@ declare class ServerPeer {
|
|
|
310
291
|
/**
|
|
311
292
|
* Handle a message from client
|
|
312
293
|
*/
|
|
313
|
-
message(message:
|
|
314
|
-
close(
|
|
294
|
+
message(message: ClientPeerSendMessage, handleRequest: (request: StandardLazyRequest) => Promise<StandardResponse>): Promise<void>;
|
|
295
|
+
close(reason?: unknown): Promise<void>;
|
|
296
|
+
private closeById;
|
|
315
297
|
}
|
|
316
298
|
|
|
317
|
-
|
|
318
|
-
|
|
299
|
+
declare function isPeerMessage(maybe: unknown): maybe is PeerMessage;
|
|
300
|
+
declare function isPeerRequestMessage(maybe: PeerMessage): maybe is PeerRequestMessage;
|
|
301
|
+
declare function isPeerResponseMessage(maybe: PeerMessage): maybe is PeerResponseMessage;
|
|
302
|
+
declare function isPeerCancelMessage(maybe: PeerMessage): maybe is PeerCancelMessage;
|
|
303
|
+
declare function isPeerEventStreamMessage(maybe: PeerMessage): maybe is PeerEventStreamMessage;
|
|
304
|
+
declare function isPeerOctetStreamMessage(maybe: PeerMessage): maybe is PeerOctetStreamMessage;
|
|
305
|
+
declare function isPeerStreamCancelMessage(maybe: PeerMessage): maybe is PeerStreamCancelMessage;
|
|
306
|
+
declare function isClientPeerSendMessage(maybe: PeerMessage): maybe is ClientPeerSendMessage;
|
|
307
|
+
declare function isServerPeerSendMessage(maybe: PeerMessage): maybe is ServerPeerSendMessage;
|
|
308
|
+
|
|
309
|
+
export { ClientPeer, EventStreamTransmitter, HibernationEventIterator, ServerPeer, decodePeerMessage, encodePeerMessage, isClientPeerSendMessage, isPeerCancelMessage, isPeerEventStreamMessage, isPeerMessage, isPeerOctetStreamMessage, isPeerRequestMessage, isPeerResponseMessage, isPeerStreamCancelMessage, isServerPeerSendMessage, toEventIterator };
|
|
310
|
+
export type { ClientPeerSendMessage, DecodePeerMessageOptions, DecodePeerMessageResult, EncodePeerMessageOptions, HibernationEventIteratorCallback, PeerCancelMessage, PeerEventStreamMessage, PeerMessage, PeerOctetStreamMessage, PeerRequestMessage, PeerResponseMessage, PeerStreamCancelMessage, ServerPeerSendMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { StandardRequest, StandardResponse } from '@standardserver/core';
|
|
2
|
-
import { AsyncIdQueueCloseOptions, AsyncCleanupFn, AsyncIteratorClass } from '@standardserver/shared';
|
|
1
|
+
import { StandardRequest, StandardResponse, StandardLazyResponse, StandardLazyRequest } from '@standardserver/core';
|
|
3
2
|
import { EventStreamMessage } from '@standardserver/core/event-stream';
|
|
3
|
+
import { Queue, AsyncCleanupFn, AsyncIteratorClass } from '@standardserver/shared';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Base interface for all peer messages.
|
|
@@ -68,17 +68,17 @@ interface PeerResponseMessage extends PeerMessage {
|
|
|
68
68
|
* - **Client → Server**: Cancel an in-flight request or stop consuming a stream.
|
|
69
69
|
* - **Server → Client**: Signal an error or premature termination.
|
|
70
70
|
*/
|
|
71
|
-
interface
|
|
71
|
+
interface PeerCancelMessage extends PeerMessage {
|
|
72
72
|
/**
|
|
73
73
|
* The kind of the message.
|
|
74
74
|
*/
|
|
75
|
-
kind: '
|
|
75
|
+
kind: 'cancel';
|
|
76
76
|
/**
|
|
77
77
|
* This message does not have a JSON payload.
|
|
78
78
|
*/
|
|
79
79
|
json?: undefined;
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* Cancel messages carry no binary payload.
|
|
82
82
|
*/
|
|
83
83
|
binary?: undefined;
|
|
84
84
|
}
|
|
@@ -164,29 +164,20 @@ interface PeerStreamCancelMessage extends PeerMessage {
|
|
|
164
164
|
*/
|
|
165
165
|
binary?: undefined;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Union of all messages a client peer may send to a server peer.
|
|
169
|
+
*/
|
|
170
|
+
type ClientPeerSendMessage = PeerRequestMessage | PeerCancelMessage | PeerEventStreamMessage | PeerOctetStreamMessage;
|
|
171
|
+
/**
|
|
172
|
+
* Union of all messages a server peer may send to a client peer.
|
|
173
|
+
*/
|
|
174
|
+
type ServerPeerSendMessage = PeerResponseMessage | PeerCancelMessage | PeerOctetStreamMessage | PeerEventStreamMessage | PeerStreamCancelMessage;
|
|
167
175
|
|
|
168
|
-
interface ClientPeerCloseOptions extends AsyncIdQueueCloseOptions {
|
|
169
|
-
}
|
|
170
176
|
declare class ClientPeer {
|
|
171
177
|
private readonly send;
|
|
172
178
|
private readonly idGenerator;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*/
|
|
176
|
-
private readonly responseMessageQueue;
|
|
177
|
-
private readonly eventStreamMessageQueue;
|
|
178
|
-
private readonly octetStreamMessageQueue;
|
|
179
|
-
/**
|
|
180
|
-
* Transmitters for event streams and octet streams
|
|
181
|
-
* Should be cancelled when needed
|
|
182
|
-
*/
|
|
183
|
-
private readonly requestEventStreamTransmitters;
|
|
184
|
-
private readonly requestOctetStreamTransmitters;
|
|
185
|
-
/**
|
|
186
|
-
* Cleanup functions invoked when the request/response is completed
|
|
187
|
-
*/
|
|
188
|
-
private readonly cleanupFns;
|
|
189
|
-
constructor(send: (message: PeerAbortMessage | PeerRequestMessage | PeerEventStreamMessage | PeerOctetStreamMessage) => Promise<void>);
|
|
179
|
+
private readonly requests;
|
|
180
|
+
constructor(send: (message: ClientPeerSendMessage) => Promise<void>);
|
|
190
181
|
/**
|
|
191
182
|
* Use to measure resources usage
|
|
192
183
|
*/
|
|
@@ -194,12 +185,14 @@ declare class ClientPeer {
|
|
|
194
185
|
/**
|
|
195
186
|
* Send a request to the server peer
|
|
196
187
|
*/
|
|
197
|
-
request(request: StandardRequest): Promise<
|
|
188
|
+
request(request: StandardRequest): Promise<StandardLazyResponse>;
|
|
198
189
|
/**
|
|
199
190
|
* Handle a message from server
|
|
200
191
|
*/
|
|
201
|
-
message(message:
|
|
202
|
-
close(
|
|
192
|
+
message(message: ServerPeerSendMessage): Promise<void>;
|
|
193
|
+
close(reason?: unknown): Promise<void>;
|
|
194
|
+
private closeById;
|
|
195
|
+
private abortById;
|
|
203
196
|
}
|
|
204
197
|
|
|
205
198
|
interface EncodePeerMessageOptions {
|
|
@@ -261,7 +254,7 @@ declare function decodePeerMessage(encoded: string | Uint8Array<ArrayBuffer>, op
|
|
|
261
254
|
* Creates an AsyncIterator from a queue of peer event-stream messages.
|
|
262
255
|
* The iterator yields normal events, throws error events, and completes on done.
|
|
263
256
|
*/
|
|
264
|
-
declare function toEventIterator(
|
|
257
|
+
declare function toEventIterator(queue: Queue<PeerEventStreamMessage>, cleanup: AsyncCleanupFn): AsyncIteratorClass<unknown>;
|
|
265
258
|
/**
|
|
266
259
|
* Transmits events to a peer event-stream.
|
|
267
260
|
*/
|
|
@@ -287,22 +280,10 @@ declare class HibernationEventIterator<T, TReturn = unknown, TNext = unknown> ex
|
|
|
287
280
|
constructor(hibernationCallback: HibernationEventIteratorCallback);
|
|
288
281
|
}
|
|
289
282
|
|
|
290
|
-
interface ServerPeerCloseOptions extends AsyncIdQueueCloseOptions {
|
|
291
|
-
}
|
|
292
283
|
declare class ServerPeer {
|
|
293
284
|
private readonly send;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
*/
|
|
297
|
-
private readonly eventStreamMessageQueue;
|
|
298
|
-
private readonly octetStreamMessageQueue;
|
|
299
|
-
private readonly eventStreamTransmitters;
|
|
300
|
-
private readonly octetStreamTransmitters;
|
|
301
|
-
/**
|
|
302
|
-
* Map of abort controllers for each request
|
|
303
|
-
*/
|
|
304
|
-
private readonly controller;
|
|
305
|
-
constructor(send: (message: PeerResponseMessage | PeerAbortMessage | PeerOctetStreamMessage | PeerEventStreamMessage | PeerStreamCancelMessage) => Promise<void>);
|
|
285
|
+
private readonly requests;
|
|
286
|
+
constructor(send: (message: ServerPeerSendMessage) => Promise<void>);
|
|
306
287
|
/**
|
|
307
288
|
* Use for measure resources usage
|
|
308
289
|
*/
|
|
@@ -310,9 +291,20 @@ declare class ServerPeer {
|
|
|
310
291
|
/**
|
|
311
292
|
* Handle a message from client
|
|
312
293
|
*/
|
|
313
|
-
message(message:
|
|
314
|
-
close(
|
|
294
|
+
message(message: ClientPeerSendMessage, handleRequest: (request: StandardLazyRequest) => Promise<StandardResponse>): Promise<void>;
|
|
295
|
+
close(reason?: unknown): Promise<void>;
|
|
296
|
+
private closeById;
|
|
315
297
|
}
|
|
316
298
|
|
|
317
|
-
|
|
318
|
-
|
|
299
|
+
declare function isPeerMessage(maybe: unknown): maybe is PeerMessage;
|
|
300
|
+
declare function isPeerRequestMessage(maybe: PeerMessage): maybe is PeerRequestMessage;
|
|
301
|
+
declare function isPeerResponseMessage(maybe: PeerMessage): maybe is PeerResponseMessage;
|
|
302
|
+
declare function isPeerCancelMessage(maybe: PeerMessage): maybe is PeerCancelMessage;
|
|
303
|
+
declare function isPeerEventStreamMessage(maybe: PeerMessage): maybe is PeerEventStreamMessage;
|
|
304
|
+
declare function isPeerOctetStreamMessage(maybe: PeerMessage): maybe is PeerOctetStreamMessage;
|
|
305
|
+
declare function isPeerStreamCancelMessage(maybe: PeerMessage): maybe is PeerStreamCancelMessage;
|
|
306
|
+
declare function isClientPeerSendMessage(maybe: PeerMessage): maybe is ClientPeerSendMessage;
|
|
307
|
+
declare function isServerPeerSendMessage(maybe: PeerMessage): maybe is ServerPeerSendMessage;
|
|
308
|
+
|
|
309
|
+
export { ClientPeer, EventStreamTransmitter, HibernationEventIterator, ServerPeer, decodePeerMessage, encodePeerMessage, isClientPeerSendMessage, isPeerCancelMessage, isPeerEventStreamMessage, isPeerMessage, isPeerOctetStreamMessage, isPeerRequestMessage, isPeerResponseMessage, isPeerStreamCancelMessage, isServerPeerSendMessage, toEventIterator };
|
|
310
|
+
export type { ClientPeerSendMessage, DecodePeerMessageOptions, DecodePeerMessageResult, EncodePeerMessageOptions, HibernationEventIteratorCallback, PeerCancelMessage, PeerEventStreamMessage, PeerMessage, PeerOctetStreamMessage, PeerRequestMessage, PeerResponseMessage, PeerStreamCancelMessage, ServerPeerSendMessage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { AsyncIteratorClass, isTypescriptObject, isAsyncIteratorObject, SequentialIdGenerator,
|
|
2
|
-
import { generateContentDisposition, flattenStandardHeader, getFilenameFromContentDisposition } from '@standardserver/core';
|
|
1
|
+
import { AsyncIteratorClass, isTypescriptObject, isAsyncIteratorObject, Queue, SequentialIdGenerator, omit, emitUnhandledRejection, AbortError, stringifyJSON } from '@standardserver/shared';
|
|
2
|
+
import { generateContentDisposition, flattenStandardHeader, getFilenameFromContentDisposition, isStandardRequest, isStandardResponse } from '@standardserver/core';
|
|
3
3
|
import { withEventIteratorEventMeta, EventIteratorErrorEvent, unwrapEventIteratorEvent } from '@standardserver/core/event-stream';
|
|
4
4
|
|
|
5
|
-
function toEventIterator(
|
|
5
|
+
function toEventIterator(queue, cleanup) {
|
|
6
6
|
return new AsyncIteratorClass(async () => {
|
|
7
7
|
while (true) {
|
|
8
|
-
const { json } = await pull();
|
|
8
|
+
const { json } = await queue.pull();
|
|
9
9
|
switch (json.event) {
|
|
10
10
|
case "message": {
|
|
11
11
|
let data = json.data;
|
|
@@ -46,61 +46,69 @@ class EventStreamTransmitter {
|
|
|
46
46
|
}
|
|
47
47
|
async transmit() {
|
|
48
48
|
while (true) {
|
|
49
|
-
let json;
|
|
50
49
|
try {
|
|
51
50
|
const item = await this.iterator.next();
|
|
52
51
|
if (this.isCompleted) {
|
|
53
52
|
return;
|
|
54
53
|
}
|
|
54
|
+
const [data, meta] = unwrapEventIteratorEvent(item.value);
|
|
55
|
+
try {
|
|
56
|
+
await this.send({
|
|
57
|
+
kind: "event-stream",
|
|
58
|
+
id: this.messageId,
|
|
59
|
+
json: { ...meta, event: item.done ? "close" : "message", data }
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (!item.done) {
|
|
63
|
+
await this.cancel();
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
55
67
|
if (item.done) {
|
|
56
68
|
this.isCompleted = true;
|
|
69
|
+
return;
|
|
57
70
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (this.isCompleted) {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof EventIteratorErrorEvent) {
|
|
76
|
+
const [resolvedError, meta] = unwrapEventIteratorEvent(error);
|
|
77
|
+
await this.send({
|
|
78
|
+
kind: "event-stream",
|
|
79
|
+
id: this.messageId,
|
|
80
|
+
json: { ...meta, event: "error", data: resolvedError.data }
|
|
81
|
+
});
|
|
65
82
|
this.isCompleted = true;
|
|
66
|
-
|
|
67
|
-
json = { ...meta, event: "error", data: resolvedError.data };
|
|
83
|
+
return;
|
|
68
84
|
} else {
|
|
69
85
|
this.isCompleted = true;
|
|
70
|
-
throw
|
|
86
|
+
throw error;
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
|
-
await this.send({
|
|
74
|
-
json,
|
|
75
|
-
kind: "event-stream",
|
|
76
|
-
id: this.messageId
|
|
77
|
-
});
|
|
78
|
-
if (this.isCompleted) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
function toOctetStream(
|
|
93
|
+
function toOctetStream(queue, cleanup) {
|
|
86
94
|
return new ReadableStream({
|
|
87
95
|
async pull(controller) {
|
|
88
96
|
try {
|
|
89
|
-
const { json, binary } = await pull();
|
|
97
|
+
const { json, binary } = await queue.pull();
|
|
90
98
|
if (binary) {
|
|
91
99
|
controller.enqueue(binary instanceof Uint8Array ? binary : new Uint8Array(await binary.arrayBuffer()));
|
|
92
100
|
}
|
|
93
101
|
if (json.close) {
|
|
94
|
-
await cleanup(
|
|
102
|
+
await cleanup({ isCancelled: false });
|
|
95
103
|
controller.close();
|
|
96
104
|
}
|
|
97
|
-
} catch (
|
|
98
|
-
await cleanup(
|
|
99
|
-
controller.error(
|
|
105
|
+
} catch (error) {
|
|
106
|
+
await cleanup({ isCancelled: false, error });
|
|
107
|
+
controller.error(error);
|
|
100
108
|
}
|
|
101
109
|
},
|
|
102
110
|
async cancel() {
|
|
103
|
-
await cleanup(
|
|
111
|
+
await cleanup({ isCancelled: true });
|
|
104
112
|
}
|
|
105
113
|
});
|
|
106
114
|
}
|
|
@@ -120,8 +128,11 @@ class OctetStreamTransmitter {
|
|
|
120
128
|
}
|
|
121
129
|
async transmit() {
|
|
122
130
|
while (true) {
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
try {
|
|
132
|
+
const { done, value } = await this.reader.read();
|
|
133
|
+
if (this.isCompleted) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
125
136
|
try {
|
|
126
137
|
await this.send({
|
|
127
138
|
json: { close: done },
|
|
@@ -130,56 +141,71 @@ class OctetStreamTransmitter {
|
|
|
130
141
|
id: this.messageId
|
|
131
142
|
});
|
|
132
143
|
} catch (err) {
|
|
133
|
-
|
|
144
|
+
if (!done) {
|
|
145
|
+
await this.cancel();
|
|
146
|
+
}
|
|
134
147
|
throw err;
|
|
135
148
|
}
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
if (done) {
|
|
150
|
+
this.isCompleted = true;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
138
154
|
this.isCompleted = true;
|
|
139
|
-
|
|
155
|
+
throw error;
|
|
140
156
|
}
|
|
141
157
|
}
|
|
142
158
|
}
|
|
143
159
|
}
|
|
144
160
|
|
|
145
|
-
|
|
161
|
+
function toStandardBody(message, cleanup) {
|
|
146
162
|
const bodyHint = flattenStandardHeader(message.json.headers["standard-server"]);
|
|
147
163
|
if (bodyHint === "event-stream") {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
cleanup
|
|
151
|
-
|
|
164
|
+
const eventStreamMessageQueue = new Queue();
|
|
165
|
+
return {
|
|
166
|
+
resolveBody: async () => toEventIterator(eventStreamMessageQueue, cleanup),
|
|
167
|
+
eventStreamMessageQueue
|
|
168
|
+
};
|
|
152
169
|
}
|
|
153
170
|
if (bodyHint === "octet-stream") {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
cleanup
|
|
157
|
-
|
|
171
|
+
const octetStreamMessageQueue = new Queue();
|
|
172
|
+
return {
|
|
173
|
+
resolveBody: async () => toOctetStream(octetStreamMessageQueue, cleanup),
|
|
174
|
+
octetStreamMessageQueue
|
|
175
|
+
};
|
|
158
176
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
const resolveBody = async () => {
|
|
178
|
+
let errorRef;
|
|
179
|
+
try {
|
|
180
|
+
if (bodyHint === "file") {
|
|
181
|
+
const contentDisposition = flattenStandardHeader(message.json.headers["content-disposition"]);
|
|
182
|
+
const filename = contentDisposition !== void 0 ? getFilenameFromContentDisposition(contentDisposition) : void 0;
|
|
183
|
+
const body = new File(message.binary ? [message.binary] : [], filename ?? "blob", {
|
|
184
|
+
type: flattenStandardHeader(message.json.headers["content-type"]) ?? "application/octet-stream"
|
|
185
|
+
});
|
|
186
|
+
return body;
|
|
187
|
+
}
|
|
188
|
+
if (bodyHint === "form-data") {
|
|
189
|
+
const res = new Response(message.binary, {
|
|
190
|
+
headers: {
|
|
191
|
+
"content-type": flattenStandardHeader(message.json.headers["content-type"]) ?? "multipart/form-data"
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const body = await res.formData();
|
|
195
|
+
return body;
|
|
196
|
+
}
|
|
197
|
+
if (bodyHint === "url-search-params" && typeof message.json.body === "string") {
|
|
198
|
+
return new URLSearchParams(message.json.body);
|
|
199
|
+
}
|
|
200
|
+
return message.json.body;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
errorRef = { value: error };
|
|
203
|
+
throw error;
|
|
204
|
+
} finally {
|
|
205
|
+
await cleanup(errorRef ? { isCancelled: false, error: errorRef.value } : { isCancelled: false });
|
|
178
206
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
await cleanup(true);
|
|
182
|
-
}
|
|
207
|
+
};
|
|
208
|
+
return { resolveBody };
|
|
183
209
|
}
|
|
184
210
|
async function encodeAtomicStandardBody(body, headers) {
|
|
185
211
|
headers = { ...headers };
|
|
@@ -218,27 +244,12 @@ class ClientPeer {
|
|
|
218
244
|
this.send = send;
|
|
219
245
|
}
|
|
220
246
|
idGenerator = new SequentialIdGenerator();
|
|
221
|
-
|
|
222
|
-
* Messages waiting to be processed
|
|
223
|
-
*/
|
|
224
|
-
responseMessageQueue = new AsyncIdQueue();
|
|
225
|
-
eventStreamMessageQueue = new AsyncIdQueue();
|
|
226
|
-
octetStreamMessageQueue = new AsyncIdQueue();
|
|
227
|
-
/**
|
|
228
|
-
* Transmitters for event streams and octet streams
|
|
229
|
-
* Should be cancelled when needed
|
|
230
|
-
*/
|
|
231
|
-
requestEventStreamTransmitters = /* @__PURE__ */ new Map();
|
|
232
|
-
requestOctetStreamTransmitters = /* @__PURE__ */ new Map();
|
|
233
|
-
/**
|
|
234
|
-
* Cleanup functions invoked when the request/response is completed
|
|
235
|
-
*/
|
|
236
|
-
cleanupFns = /* @__PURE__ */ new Map();
|
|
247
|
+
requests = /* @__PURE__ */ new Map();
|
|
237
248
|
/**
|
|
238
249
|
* Use to measure resources usage
|
|
239
250
|
*/
|
|
240
251
|
get size() {
|
|
241
|
-
return this.
|
|
252
|
+
return this.requests.size;
|
|
242
253
|
}
|
|
243
254
|
/**
|
|
244
255
|
* Send a request to the server peer
|
|
@@ -247,162 +258,228 @@ class ClientPeer {
|
|
|
247
258
|
const signal = request.signal;
|
|
248
259
|
signal?.throwIfAborted();
|
|
249
260
|
const id = this.idGenerator.generate();
|
|
250
|
-
|
|
251
|
-
this.
|
|
252
|
-
this.responseMessageQueue.open(id);
|
|
261
|
+
const state = {};
|
|
262
|
+
this.requests.set(id, state);
|
|
253
263
|
let abortListener;
|
|
254
|
-
signal?.addEventListener("abort", abortListener =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
*
|
|
259
|
-
* We don't need to check if is there any abort message already sent
|
|
260
|
-
* since this listener is removed when the request is closed.
|
|
261
|
-
*/
|
|
262
|
-
this.send({ id, kind: "abort" }),
|
|
263
|
-
this.close({ id, reason: signal.reason })
|
|
264
|
-
]);
|
|
264
|
+
signal?.addEventListener("abort", abortListener = () => this.abortById(id, signal.reason));
|
|
265
|
+
state.cleanupFns ??= [];
|
|
266
|
+
state.cleanupFns.push(() => {
|
|
267
|
+
signal?.removeEventListener("abort", abortListener);
|
|
265
268
|
});
|
|
266
|
-
const cleanupFns = [
|
|
267
|
-
/**
|
|
268
|
-
* Make sure to remove the abort listener when the request/response is closed.
|
|
269
|
-
* Since a signal can be reused for multiple requests, if each request
|
|
270
|
-
* adds listeners without removing them, it can lead to excessive memory usage
|
|
271
|
-
* until the signal is garbage collected.
|
|
272
|
-
*/
|
|
273
|
-
() => {
|
|
274
|
-
signal?.removeEventListener("abort", abortListener);
|
|
275
|
-
}
|
|
276
|
-
];
|
|
277
|
-
this.cleanupFns.set(id, cleanupFns);
|
|
278
269
|
try {
|
|
279
270
|
const [jsonBody, headers, binary] = await encodeAtomicStandardBody(request.body, request.headers);
|
|
280
271
|
signal?.throwIfAborted();
|
|
281
|
-
|
|
272
|
+
await this.send({
|
|
282
273
|
id,
|
|
283
274
|
kind: "request",
|
|
284
275
|
json: {
|
|
285
|
-
...request,
|
|
276
|
+
...omit(request, ["signal"]),
|
|
286
277
|
headers,
|
|
287
|
-
body: jsonBody
|
|
288
|
-
...{ signal: void 0 }
|
|
289
|
-
// remove signal from request
|
|
278
|
+
body: jsonBody
|
|
290
279
|
},
|
|
291
280
|
binary
|
|
292
|
-
};
|
|
293
|
-
await this.send(requestMessage);
|
|
281
|
+
});
|
|
294
282
|
signal?.throwIfAborted();
|
|
295
283
|
if (isAsyncIteratorObject(request.body)) {
|
|
296
284
|
const transmitter = new EventStreamTransmitter(request.body, id, this.send);
|
|
297
|
-
|
|
298
|
-
void transmitter.transmit().catch(async (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
this.requestEventStreamTransmitters.has(id) ? this.send({ id, kind: "abort" }) : void 0,
|
|
305
|
-
this.close({ id, reason })
|
|
306
|
-
]);
|
|
285
|
+
state.eventStreamTransmitter = transmitter;
|
|
286
|
+
void transmitter.transmit().catch(async (error) => {
|
|
287
|
+
if (state.eventStreamTransmitter) {
|
|
288
|
+
await this.abortById(id, error);
|
|
289
|
+
} else {
|
|
290
|
+
emitUnhandledRejection(error);
|
|
291
|
+
}
|
|
307
292
|
});
|
|
308
293
|
} else if (request.body instanceof ReadableStream) {
|
|
309
294
|
const transmitter = new OctetStreamTransmitter(request.body, id, this.send);
|
|
310
|
-
|
|
311
|
-
void transmitter.transmit().catch(async (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
this.close({ id, reason })
|
|
319
|
-
]);
|
|
295
|
+
state.octetStreamTransmitter = transmitter;
|
|
296
|
+
void transmitter.transmit().catch(async (error) => {
|
|
297
|
+
if (state.octetStreamTransmitter) {
|
|
298
|
+
await this.abortById(id, error);
|
|
299
|
+
} else {
|
|
300
|
+
emitUnhandledRejection(error);
|
|
301
|
+
}
|
|
302
|
+
/* v8 ignore stop -- @preserve */
|
|
320
303
|
});
|
|
321
304
|
}
|
|
322
|
-
const peerResponseMessage = await this.responseMessageQueue.pull(id);
|
|
323
|
-
return {
|
|
324
|
-
...peerResponseMessage.json,
|
|
325
|
-
body: await toStandardBody(
|
|
326
|
-
peerResponseMessage,
|
|
327
|
-
this.eventStreamMessageQueue,
|
|
328
|
-
this.octetStreamMessageQueue,
|
|
329
|
-
async (isCompleted) => {
|
|
330
|
-
await Promise.all([
|
|
331
|
-
/**
|
|
332
|
-
* We don't need to send abort message if completed
|
|
333
|
-
* or request was aborted
|
|
334
|
-
*/
|
|
335
|
-
!isCompleted && (this.eventStreamMessageQueue.isOpen(id) || this.octetStreamMessageQueue.isOpen(id)) ? this.send({ id, kind: "abort" }) : void 0,
|
|
336
|
-
this.close({ id })
|
|
337
|
-
]);
|
|
338
|
-
}
|
|
339
|
-
)
|
|
340
|
-
};
|
|
341
305
|
} catch (reason) {
|
|
342
|
-
await this.
|
|
306
|
+
await this.closeById(id, reason);
|
|
343
307
|
throw reason;
|
|
344
308
|
}
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
state.resolve = resolve;
|
|
311
|
+
state.reject = reject;
|
|
312
|
+
});
|
|
345
313
|
}
|
|
346
314
|
/**
|
|
347
315
|
* Handle a message from server
|
|
348
316
|
*/
|
|
349
317
|
async message(message) {
|
|
318
|
+
const id = message.id;
|
|
319
|
+
const state = this.requests.get(id);
|
|
320
|
+
if (!state) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
350
323
|
if (message.kind === "stream/cancel") {
|
|
351
324
|
const promise = Promise.all([
|
|
352
|
-
|
|
353
|
-
|
|
325
|
+
state.eventStreamTransmitter?.cancel(),
|
|
326
|
+
state.octetStreamTransmitter?.cancel()
|
|
354
327
|
]);
|
|
355
|
-
|
|
356
|
-
|
|
328
|
+
state.eventStreamTransmitter = void 0;
|
|
329
|
+
state.octetStreamTransmitter = void 0;
|
|
357
330
|
await promise;
|
|
358
331
|
return;
|
|
359
332
|
}
|
|
360
|
-
if (message.kind === "
|
|
361
|
-
await this.
|
|
333
|
+
if (message.kind === "cancel") {
|
|
334
|
+
await this.closeById(id, new AbortError("Server canceled the request"));
|
|
362
335
|
return;
|
|
363
336
|
}
|
|
364
337
|
if (message.kind === "event-stream") {
|
|
365
|
-
|
|
366
|
-
this.eventStreamMessageQueue.push(message.id, message);
|
|
367
|
-
}
|
|
338
|
+
state.eventStreamMessageQueue?.push(message);
|
|
368
339
|
return;
|
|
369
340
|
}
|
|
370
341
|
if (message.kind === "octet-stream") {
|
|
371
|
-
|
|
372
|
-
this.octetStreamMessageQueue.push(message.id, message);
|
|
373
|
-
}
|
|
342
|
+
state.octetStreamMessageQueue?.push(message);
|
|
374
343
|
return;
|
|
375
344
|
}
|
|
376
|
-
if (
|
|
377
|
-
|
|
345
|
+
if (!state.resolve) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const resolve = state.resolve;
|
|
349
|
+
state.resolve = void 0;
|
|
350
|
+
try {
|
|
351
|
+
const decoded = toStandardBody(message, async ({ isCancelled, error }) => {
|
|
352
|
+
if (isCancelled) {
|
|
353
|
+
await this.abortById(id, error);
|
|
354
|
+
} else if (state.eventStreamMessageQueue || state.octetStreamMessageQueue) {
|
|
355
|
+
await this.closeById(id, error);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
state.eventStreamMessageQueue = decoded.eventStreamMessageQueue;
|
|
359
|
+
state.octetStreamMessageQueue = decoded.octetStreamMessageQueue;
|
|
360
|
+
resolve({ ...message.json, resolveBody: decoded.resolveBody });
|
|
361
|
+
if (!state.eventStreamMessageQueue && !state.octetStreamMessageQueue) {
|
|
362
|
+
await this.closeById(id);
|
|
363
|
+
}
|
|
364
|
+
} catch (reason) {
|
|
365
|
+
await this.closeById(id, reason);
|
|
378
366
|
}
|
|
379
367
|
}
|
|
380
|
-
async close(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
368
|
+
async close(reason) {
|
|
369
|
+
reason ??= new AbortError("Peer was closed");
|
|
370
|
+
await Promise.all(
|
|
371
|
+
Array.from(this.requests.keys()).map((id) => this.closeById(id, reason))
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
async closeById(id, reason) {
|
|
375
|
+
const state = this.requests.get(id);
|
|
376
|
+
if (!state) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.requests.delete(id);
|
|
380
|
+
reason ??= new AbortError("Request was closed");
|
|
381
|
+
state.reject?.(reason);
|
|
382
|
+
state.resolve = void 0;
|
|
383
|
+
state.reject = void 0;
|
|
384
|
+
state.eventStreamMessageQueue?.close(reason);
|
|
385
|
+
state.octetStreamMessageQueue?.close(reason);
|
|
386
|
+
state.eventStreamMessageQueue = void 0;
|
|
387
|
+
state.octetStreamMessageQueue = void 0;
|
|
388
|
+
const promises = [
|
|
389
|
+
state.eventStreamTransmitter?.cancel(),
|
|
390
|
+
state.octetStreamTransmitter?.cancel()
|
|
391
|
+
];
|
|
392
|
+
state.eventStreamTransmitter = void 0;
|
|
393
|
+
state.octetStreamTransmitter = void 0;
|
|
394
|
+
state.cleanupFns?.forEach((fn) => fn());
|
|
395
|
+
state.cleanupFns = void 0;
|
|
396
|
+
await Promise.all(promises);
|
|
397
|
+
}
|
|
398
|
+
async abortById(id, reason) {
|
|
399
|
+
const state = this.requests.get(id);
|
|
400
|
+
if (!state) {
|
|
401
|
+
return;
|
|
401
402
|
}
|
|
403
|
+
this.requests.delete(id);
|
|
404
|
+
reason ??= new AbortError("Request was aborted");
|
|
405
|
+
state.reject?.(reason);
|
|
406
|
+
state.resolve = void 0;
|
|
407
|
+
state.reject = void 0;
|
|
408
|
+
state.eventStreamMessageQueue?.abort(reason);
|
|
409
|
+
state.octetStreamMessageQueue?.abort(reason);
|
|
410
|
+
state.eventStreamMessageQueue = void 0;
|
|
411
|
+
state.octetStreamMessageQueue = void 0;
|
|
412
|
+
const promises = [
|
|
413
|
+
this.send({ id, kind: "cancel" }),
|
|
414
|
+
state.eventStreamTransmitter?.cancel(),
|
|
415
|
+
state.octetStreamTransmitter?.cancel()
|
|
416
|
+
];
|
|
417
|
+
state.eventStreamTransmitter = void 0;
|
|
418
|
+
state.octetStreamTransmitter = void 0;
|
|
419
|
+
state.cleanupFns?.forEach((fn) => fn());
|
|
420
|
+
state.cleanupFns = void 0;
|
|
402
421
|
await Promise.all(promises);
|
|
403
422
|
}
|
|
404
423
|
}
|
|
405
424
|
|
|
425
|
+
function isPeerMessage(maybe) {
|
|
426
|
+
if (!isTypescriptObject(maybe)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
if (typeof maybe.id !== "string") {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
if (typeof maybe.kind !== "string") {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
if (maybe.binary !== void 0 && !(maybe.binary instanceof Uint8Array) && !(maybe.binary instanceof Blob)) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
function isPeerRequestMessage(maybe) {
|
|
441
|
+
return maybe.kind === "request" && isStandardRequest(maybe.json);
|
|
442
|
+
}
|
|
443
|
+
function isPeerResponseMessage(maybe) {
|
|
444
|
+
return maybe.kind === "response" && isStandardResponse(maybe.json);
|
|
445
|
+
}
|
|
446
|
+
function isPeerCancelMessage(maybe) {
|
|
447
|
+
return maybe.kind === "cancel" && maybe.json === void 0 && maybe.binary === void 0;
|
|
448
|
+
}
|
|
449
|
+
function isPeerEventStreamMessage(maybe) {
|
|
450
|
+
if (maybe.kind !== "event-stream" || maybe.binary !== void 0) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
if (!isTypescriptObject(maybe.json)) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
if (maybe.json.id !== void 0 && typeof maybe.json.id !== "string") {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
if (maybe.json.event !== void 0 && typeof maybe.json.event !== "string") {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
if (maybe.json.retry !== void 0 && !Number.isFinite(maybe.json.retry)) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
if (maybe.json.comments !== void 0 && !(Array.isArray(maybe.json.comments) && maybe.json.comments.every((v) => typeof v === "string"))) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
function isPeerOctetStreamMessage(maybe) {
|
|
471
|
+
return maybe.kind === "octet-stream" && isTypescriptObject(maybe.json) && typeof maybe.json.close === "boolean";
|
|
472
|
+
}
|
|
473
|
+
function isPeerStreamCancelMessage(maybe) {
|
|
474
|
+
return maybe.kind === "stream/cancel" && maybe.json === void 0 && maybe.binary === void 0;
|
|
475
|
+
}
|
|
476
|
+
function isClientPeerSendMessage(maybe) {
|
|
477
|
+
return isPeerRequestMessage(maybe) || isPeerCancelMessage(maybe) || isPeerEventStreamMessage(maybe) || isPeerOctetStreamMessage(maybe);
|
|
478
|
+
}
|
|
479
|
+
function isServerPeerSendMessage(maybe) {
|
|
480
|
+
return isPeerResponseMessage(maybe) || isPeerCancelMessage(maybe) || isPeerEventStreamMessage(maybe) || isPeerOctetStreamMessage(maybe) || isPeerStreamCancelMessage(maybe);
|
|
481
|
+
}
|
|
482
|
+
|
|
406
483
|
const JSON_BINARY_DELIMITER = 255;
|
|
407
484
|
const textEncoder = new TextEncoder();
|
|
408
485
|
const textDecoder = new TextDecoder();
|
|
@@ -424,37 +501,48 @@ async function encodePeerMessage(message, options = {}) {
|
|
|
424
501
|
return output;
|
|
425
502
|
}
|
|
426
503
|
function decodePeerMessage(encoded, options = {}) {
|
|
427
|
-
|
|
428
|
-
if (
|
|
429
|
-
if (
|
|
504
|
+
try {
|
|
505
|
+
if (typeof encoded === "string") {
|
|
506
|
+
if (options.prefix) {
|
|
507
|
+
if (!encoded.startsWith(options.prefix)) {
|
|
508
|
+
return { matched: false };
|
|
509
|
+
}
|
|
510
|
+
encoded = encoded.slice(options.prefix.length);
|
|
511
|
+
}
|
|
512
|
+
const message2 = JSON.parse(encoded);
|
|
513
|
+
if (!isPeerMessage(message2)) {
|
|
430
514
|
return { matched: false };
|
|
431
515
|
}
|
|
432
|
-
|
|
516
|
+
return { matched: true, message: message2 };
|
|
433
517
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
518
|
+
if (options.prefix) {
|
|
519
|
+
const prefixBytes = textEncoder.encode(options.prefix);
|
|
520
|
+
for (let i = 0; i < prefixBytes.length; i++) {
|
|
521
|
+
if (encoded[i] !== prefixBytes[i]) {
|
|
522
|
+
return { matched: false };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
encoded = encoded.subarray(prefixBytes.length);
|
|
526
|
+
}
|
|
527
|
+
const separatorIndex = encoded.indexOf(JSON_BINARY_DELIMITER);
|
|
528
|
+
if (separatorIndex === -1) {
|
|
529
|
+
const message2 = JSON.parse(textDecoder.decode(encoded));
|
|
530
|
+
if (!isPeerMessage(message2)) {
|
|
440
531
|
return { matched: false };
|
|
441
532
|
}
|
|
533
|
+
return { matched: true, message: message2 };
|
|
442
534
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
const jsonBytes = encoded.subarray(0, separatorIndex);
|
|
450
|
-
const binaryBytes = encoded.subarray(separatorIndex + 1);
|
|
451
|
-
return {
|
|
452
|
-
matched: true,
|
|
453
|
-
message: {
|
|
454
|
-
...JSON.parse(textDecoder.decode(jsonBytes)),
|
|
455
|
-
binary: binaryBytes
|
|
535
|
+
const jsonBytes = encoded.subarray(0, separatorIndex);
|
|
536
|
+
const binaryBytes = encoded.subarray(separatorIndex + 1);
|
|
537
|
+
const message = JSON.parse(textDecoder.decode(jsonBytes));
|
|
538
|
+
if (!isPeerMessage(message)) {
|
|
539
|
+
return { matched: false };
|
|
456
540
|
}
|
|
457
|
-
|
|
541
|
+
message.binary = binaryBytes;
|
|
542
|
+
return { matched: true, message };
|
|
543
|
+
} catch {
|
|
544
|
+
return { matched: false };
|
|
545
|
+
}
|
|
458
546
|
}
|
|
459
547
|
|
|
460
548
|
class HibernationEventIterator extends AsyncIteratorClass {
|
|
@@ -466,8 +554,8 @@ class HibernationEventIterator extends AsyncIteratorClass {
|
|
|
466
554
|
constructor(hibernationCallback) {
|
|
467
555
|
super(async () => {
|
|
468
556
|
throw new Error("Cannot use hibernating iterator directly");
|
|
469
|
-
}, async (
|
|
470
|
-
if (
|
|
557
|
+
}, async ({ isCancelled }) => {
|
|
558
|
+
if (isCancelled) {
|
|
471
559
|
throw new Error("Cannot use hibernating iterator directly");
|
|
472
560
|
}
|
|
473
561
|
});
|
|
@@ -479,66 +567,48 @@ class ServerPeer {
|
|
|
479
567
|
constructor(send) {
|
|
480
568
|
this.send = send;
|
|
481
569
|
}
|
|
482
|
-
|
|
483
|
-
* Messages waiting to be processed
|
|
484
|
-
*/
|
|
485
|
-
eventStreamMessageQueue = new AsyncIdQueue();
|
|
486
|
-
octetStreamMessageQueue = new AsyncIdQueue();
|
|
487
|
-
eventStreamTransmitters = /* @__PURE__ */ new Map();
|
|
488
|
-
octetStreamTransmitters = /* @__PURE__ */ new Map();
|
|
489
|
-
/**
|
|
490
|
-
* Map of abort controllers for each request
|
|
491
|
-
*/
|
|
492
|
-
controller = /* @__PURE__ */ new Map();
|
|
570
|
+
requests = /* @__PURE__ */ new Map();
|
|
493
571
|
/**
|
|
494
572
|
* Use for measure resources usage
|
|
495
573
|
*/
|
|
496
574
|
get size() {
|
|
497
|
-
return this.
|
|
575
|
+
return this.requests.size;
|
|
498
576
|
}
|
|
499
577
|
/**
|
|
500
578
|
* Handle a message from client
|
|
501
579
|
*/
|
|
502
580
|
async message(message, handleRequest) {
|
|
503
|
-
|
|
504
|
-
|
|
581
|
+
const id = message.id;
|
|
582
|
+
if (message.kind === "cancel") {
|
|
583
|
+
await this.closeById(id, new AbortError("Client aborted the request"));
|
|
505
584
|
return;
|
|
506
585
|
}
|
|
507
586
|
if (message.kind === "event-stream") {
|
|
508
|
-
|
|
509
|
-
this.eventStreamMessageQueue.push(message.id, message);
|
|
510
|
-
}
|
|
587
|
+
this.requests.get(id)?.eventStreamMessageQueue?.push(message);
|
|
511
588
|
return;
|
|
512
589
|
}
|
|
513
590
|
if (message.kind === "octet-stream") {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
591
|
+
this.requests.get(id)?.octetStreamMessageQueue?.push(message);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (this.requests.has(id)) {
|
|
517
595
|
return;
|
|
518
596
|
}
|
|
519
|
-
this.eventStreamMessageQueue.open(message.id);
|
|
520
|
-
this.octetStreamMessageQueue.open(message.id);
|
|
521
597
|
const controller = new AbortController();
|
|
522
|
-
|
|
598
|
+
const state = { controller };
|
|
599
|
+
this.requests.set(id, state);
|
|
523
600
|
const signal = controller.signal;
|
|
524
601
|
try {
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!isCompleted && this.controller.has(message.id)) {
|
|
536
|
-
await this.send({ id: message.id, kind: "stream/cancel" });
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
)
|
|
540
|
-
};
|
|
541
|
-
const response = await handleRequest(request);
|
|
602
|
+
const decoded = toStandardBody(message, async ({ isCancelled }) => {
|
|
603
|
+
if (isCancelled && (state.eventStreamMessageQueue || state.octetStreamMessageQueue)) {
|
|
604
|
+
state.eventStreamMessageQueue = void 0;
|
|
605
|
+
state.octetStreamMessageQueue = void 0;
|
|
606
|
+
await this.send({ id, kind: "stream/cancel" });
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
state.eventStreamMessageQueue = decoded.eventStreamMessageQueue;
|
|
610
|
+
state.octetStreamMessageQueue = decoded.octetStreamMessageQueue;
|
|
611
|
+
const response = await handleRequest({ ...message.json, signal, resolveBody: decoded.resolveBody });
|
|
542
612
|
if (signal.aborted) {
|
|
543
613
|
return;
|
|
544
614
|
}
|
|
@@ -549,13 +619,7 @@ class ServerPeer {
|
|
|
549
619
|
const responseMessage = {
|
|
550
620
|
id: message.id,
|
|
551
621
|
kind: "response",
|
|
552
|
-
json: {
|
|
553
|
-
...response,
|
|
554
|
-
headers,
|
|
555
|
-
body: jsonBody,
|
|
556
|
-
...{ signal: void 0 }
|
|
557
|
-
// remove signal from request
|
|
558
|
-
},
|
|
622
|
+
json: { ...response, headers, body: jsonBody },
|
|
559
623
|
binary
|
|
560
624
|
};
|
|
561
625
|
await this.send(responseMessage);
|
|
@@ -567,50 +631,54 @@ class ServerPeer {
|
|
|
567
631
|
response.body.hibernationCallback?.(message.id);
|
|
568
632
|
} else {
|
|
569
633
|
const transmitter = new EventStreamTransmitter(response.body, message.id, this.send);
|
|
570
|
-
|
|
634
|
+
state.eventStreamTransmitter = transmitter;
|
|
571
635
|
await transmitter.transmit();
|
|
572
636
|
}
|
|
573
637
|
} else if (response.body instanceof ReadableStream) {
|
|
574
638
|
const transmitter = new OctetStreamTransmitter(response.body, message.id, this.send);
|
|
575
|
-
|
|
639
|
+
state.octetStreamTransmitter = transmitter;
|
|
576
640
|
await transmitter.transmit();
|
|
577
641
|
}
|
|
578
|
-
|
|
579
|
-
await this.
|
|
642
|
+
state.controller = void 0;
|
|
643
|
+
await this.closeById(id);
|
|
580
644
|
} catch (reason) {
|
|
581
645
|
await Promise.all([
|
|
582
646
|
/**
|
|
583
|
-
* Do not need to send
|
|
647
|
+
* Do not need to send cancel message if request was closed or aborted
|
|
584
648
|
*/
|
|
585
|
-
this.
|
|
586
|
-
this.
|
|
649
|
+
this.requests.has(message.id) ? this.send({ id: message.id, kind: "cancel" }) : void 0,
|
|
650
|
+
this.closeById(id, reason)
|
|
587
651
|
]);
|
|
588
652
|
throw reason;
|
|
589
653
|
}
|
|
590
654
|
}
|
|
591
|
-
async close(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
this.controller.clear();
|
|
602
|
-
} else {
|
|
603
|
-
promises.push(
|
|
604
|
-
this.eventStreamTransmitters.get(options.id)?.cancel(),
|
|
605
|
-
this.octetStreamTransmitters.get(options.id)?.cancel()
|
|
606
|
-
);
|
|
607
|
-
this.eventStreamTransmitters.delete(options.id);
|
|
608
|
-
this.octetStreamTransmitters.delete(options.id);
|
|
609
|
-
this.controller.get(options.id)?.abort(options.reason);
|
|
610
|
-
this.controller.delete(options.id);
|
|
655
|
+
async close(reason) {
|
|
656
|
+
reason ??= new AbortError("Peer was closed");
|
|
657
|
+
await Promise.all(
|
|
658
|
+
Array.from(this.requests.keys()).map((id) => this.closeById(id, reason))
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
async closeById(id, reason) {
|
|
662
|
+
const state = this.requests.get(id);
|
|
663
|
+
if (!state) {
|
|
664
|
+
return;
|
|
611
665
|
}
|
|
666
|
+
this.requests.delete(id);
|
|
667
|
+
reason ??= new AbortError("Request was closed");
|
|
668
|
+
state.controller?.abort(reason);
|
|
669
|
+
state.controller = void 0;
|
|
670
|
+
state.eventStreamMessageQueue?.close(reason);
|
|
671
|
+
state.octetStreamMessageQueue?.close(reason);
|
|
672
|
+
state.eventStreamMessageQueue = void 0;
|
|
673
|
+
state.octetStreamMessageQueue = void 0;
|
|
674
|
+
const promises = [
|
|
675
|
+
state.eventStreamTransmitter?.cancel(),
|
|
676
|
+
state.octetStreamTransmitter?.cancel()
|
|
677
|
+
];
|
|
678
|
+
state.eventStreamTransmitter = void 0;
|
|
679
|
+
state.octetStreamTransmitter = void 0;
|
|
612
680
|
await Promise.all(promises);
|
|
613
681
|
}
|
|
614
682
|
}
|
|
615
683
|
|
|
616
|
-
export { ClientPeer, EventStreamTransmitter, HibernationEventIterator, ServerPeer, decodePeerMessage, encodePeerMessage, toEventIterator };
|
|
684
|
+
export { ClientPeer, EventStreamTransmitter, HibernationEventIterator, ServerPeer, decodePeerMessage, encodePeerMessage, isClientPeerSendMessage, isPeerCancelMessage, isPeerEventStreamMessage, isPeerMessage, isPeerOctetStreamMessage, isPeerRequestMessage, isPeerResponseMessage, isPeerStreamCancelMessage, isServerPeerSendMessage, toEventIterator };
|
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.8",
|
|
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.8",
|
|
24
|
+
"@standardserver/shared": "0.0.8"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "unbuild",
|