@genation/bridge-peer 0.1.1 → 0.1.3
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/GenationBridgePeer.podspec +1 -1
- package/package.json +2 -2
- package/react-native/android/src/main/jniLibs/arm64-v8a/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/android/src/main/jniLibs/armeabi-v7a/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/android/src/main/jniLibs/x86/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/android/src/main/jniLibs/x86_64/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64/libgenation_bridge_crypto_uniffi.a +0 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64_x86_64-simulator/libgenation_bridge_crypto_uniffi.a +0 -0
- package/src/node/crypto-core/javascript/provider.js +2 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm.d.ts +10 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm.js +35 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm_bg.wasm +0 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm_bg.wasm.d.ts +2 -0
- package/src/relay/runtime.js +72 -35
- package/src/relay/unary-session-cache.js +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genation/bridge-peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Node.js, Electron, and React Native peer SDK for Genation Bridge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@genation/bridge-contracts": "0.1.
|
|
49
|
+
"@genation/bridge-contracts": "0.1.3",
|
|
50
50
|
"jose": "^6.2.10",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
Binary file
|
package/react-native/android/src/main/jniLibs/armeabi-v7a/libgenation_bridge_crypto_uniffi.so
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -14,6 +14,7 @@ import { loadBridgeCryptoCore } from "./mod.js";
|
|
|
14
14
|
return Object.freeze({
|
|
15
15
|
beginHandshake: ()=>session.begin_handshake(),
|
|
16
16
|
finishHandshake: (message)=>session.finish_handshake(message),
|
|
17
|
+
finishHandshakeWithPayload: (message)=>session.finish_handshake_with_payload(message),
|
|
17
18
|
encrypt: (aad, plaintext)=>session.encrypt(aad, plaintext),
|
|
18
19
|
decrypt: (aad, ciphertext)=>session.decrypt(aad, ciphertext),
|
|
19
20
|
close: ()=>{
|
|
@@ -29,6 +30,7 @@ import { loadBridgeCryptoCore } from "./mod.js";
|
|
|
29
30
|
let closed = false;
|
|
30
31
|
return Object.freeze({
|
|
31
32
|
acceptHandshake: (message)=>session.accept_handshake(message),
|
|
33
|
+
acceptHandshakeWithPayload: (message, payload)=>session.accept_handshake_with_payload(message, payload),
|
|
32
34
|
encrypt: (aad, plaintext)=>session.encrypt(aad, plaintext),
|
|
33
35
|
decrypt: (aad, ciphertext)=>session.decrypt(aad, ciphertext),
|
|
34
36
|
close: ()=>{
|
|
@@ -27,6 +27,10 @@ export class Initiator {
|
|
|
27
27
|
* Read handshake message two.
|
|
28
28
|
*/
|
|
29
29
|
finish_handshake(message: Uint8Array): void;
|
|
30
|
+
/**
|
|
31
|
+
* Read handshake message two and return its authenticated payload.
|
|
32
|
+
*/
|
|
33
|
+
finish_handshake_with_payload(message: Uint8Array): Uint8Array;
|
|
30
34
|
/**
|
|
31
35
|
* Create an invoking-peer state.
|
|
32
36
|
*/
|
|
@@ -43,6 +47,10 @@ export class Responder {
|
|
|
43
47
|
* Read handshake one, write handshake two, and enter transport mode.
|
|
44
48
|
*/
|
|
45
49
|
accept_handshake(message: Uint8Array): Uint8Array;
|
|
50
|
+
/**
|
|
51
|
+
* Read handshake one and authenticate a payload inside handshake two.
|
|
52
|
+
*/
|
|
53
|
+
accept_handshake_with_payload(message: Uint8Array, payload: Uint8Array): Uint8Array;
|
|
46
54
|
/**
|
|
47
55
|
* Dispose session state.
|
|
48
56
|
*/
|
|
@@ -83,9 +91,11 @@ export interface InitOutput {
|
|
|
83
91
|
readonly initiator_decrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
84
92
|
readonly initiator_encrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
85
93
|
readonly initiator_finish_handshake: (a: number, b: number, c: number) => [number, number];
|
|
94
|
+
readonly initiator_finish_handshake_with_payload: (a: number, b: number, c: number) => [number, number, number, number];
|
|
86
95
|
readonly initiator_new: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
|
|
87
96
|
readonly noise_profile: () => [number, number];
|
|
88
97
|
readonly responder_accept_handshake: (a: number, b: number, c: number) => [number, number, number, number];
|
|
98
|
+
readonly responder_accept_handshake_with_payload: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
89
99
|
readonly responder_close: (a: number) => void;
|
|
90
100
|
readonly responder_decrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
91
101
|
readonly responder_encrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
@@ -83,6 +83,22 @@ export class Initiator {
|
|
|
83
83
|
throw takeFromExternrefTable0(ret[0]);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Read handshake message two and return its authenticated payload.
|
|
88
|
+
* @param {Uint8Array} message
|
|
89
|
+
* @returns {Uint8Array}
|
|
90
|
+
*/
|
|
91
|
+
finish_handshake_with_payload(message) {
|
|
92
|
+
const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);
|
|
93
|
+
const len0 = WASM_VECTOR_LEN;
|
|
94
|
+
const ret = wasm.initiator_finish_handshake_with_payload(this.__wbg_ptr, ptr0, len0);
|
|
95
|
+
if (ret[3]) {
|
|
96
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
97
|
+
}
|
|
98
|
+
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
99
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
100
|
+
return v2;
|
|
101
|
+
}
|
|
86
102
|
/**
|
|
87
103
|
* Create an invoking-peer state.
|
|
88
104
|
* @param {Uint8Array} prologue
|
|
@@ -137,6 +153,25 @@ export class Responder {
|
|
|
137
153
|
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
138
154
|
return v2;
|
|
139
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Read handshake one and authenticate a payload inside handshake two.
|
|
158
|
+
* @param {Uint8Array} message
|
|
159
|
+
* @param {Uint8Array} payload
|
|
160
|
+
* @returns {Uint8Array}
|
|
161
|
+
*/
|
|
162
|
+
accept_handshake_with_payload(message, payload) {
|
|
163
|
+
const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);
|
|
164
|
+
const len0 = WASM_VECTOR_LEN;
|
|
165
|
+
const ptr1 = passArray8ToWasm0(payload, wasm.__wbindgen_malloc);
|
|
166
|
+
const len1 = WASM_VECTOR_LEN;
|
|
167
|
+
const ret = wasm.responder_accept_handshake_with_payload(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
|
168
|
+
if (ret[3]) {
|
|
169
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
170
|
+
}
|
|
171
|
+
var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
172
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
173
|
+
return v3;
|
|
174
|
+
}
|
|
140
175
|
/**
|
|
141
176
|
* Dispose session state.
|
|
142
177
|
*/
|
|
Binary file
|
|
@@ -9,9 +9,11 @@ export const initiator_close: (a: number) => void;
|
|
|
9
9
|
export const initiator_decrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
10
10
|
export const initiator_encrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
11
11
|
export const initiator_finish_handshake: (a: number, b: number, c: number) => [number, number];
|
|
12
|
+
export const initiator_finish_handshake_with_payload: (a: number, b: number, c: number) => [number, number, number, number];
|
|
12
13
|
export const initiator_new: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
|
|
13
14
|
export const noise_profile: () => [number, number];
|
|
14
15
|
export const responder_accept_handshake: (a: number, b: number, c: number) => [number, number, number, number];
|
|
16
|
+
export const responder_accept_handshake_with_payload: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
15
17
|
export const responder_close: (a: number) => void;
|
|
16
18
|
export const responder_decrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
17
19
|
export const responder_encrypt: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
package/src/relay/runtime.js
CHANGED
|
@@ -2,7 +2,10 @@ import { buildInvocationAad, buildSessionPrologue, permitsCapability } from "@ge
|
|
|
2
2
|
import { BridgePeerError } from "../core/mod.js";
|
|
3
3
|
import { decodeBase64url, encodeBase64url, parseRelayGrantClaims } from "./encoding.js";
|
|
4
4
|
import { RelayFrameChannel } from "./frame-channel.js";
|
|
5
|
+
import { createUnarySessionCache } from "./unary-session-cache.js";
|
|
5
6
|
const encoder = new TextEncoder();
|
|
7
|
+
const unaryReuseHandshakePayload = encoder.encode("bridge.unary-reuse.v1");
|
|
8
|
+
const unaryReuseSessionPrefix = "sesr1_";
|
|
6
9
|
const bridgeCodes = new Set([
|
|
7
10
|
"OK",
|
|
8
11
|
"TARGET_OFFLINE",
|
|
@@ -23,6 +26,9 @@ const bridgeCodes = new Set([
|
|
|
23
26
|
const newId = (prefix)=>`${prefix}_${crypto.randomUUID()}`;
|
|
24
27
|
const remaining = (deadlineAt)=>Math.max(1, deadlineAt - Date.now());
|
|
25
28
|
const maxStreamPlaintextChunkBytes = 48 * 1_024;
|
|
29
|
+
/** Maximum number of completed direct unary sessions retained by one live peer. */ const maxIdleUnarySessions = 32;
|
|
30
|
+
/** Short initiator idle window used for immediate sequential unary bursts. */ const unarySessionIdleMs = 1_000;
|
|
31
|
+
/** Responder margin that remains open after the initiator stops session reuse. */ const unaryResponderIdleMs = 2_000;
|
|
26
32
|
const encodeResponseMetadata = (response)=>{
|
|
27
33
|
const value = Object.freeze({
|
|
28
34
|
status: response.status,
|
|
@@ -118,6 +124,7 @@ const obtainGrant = async (input, identity)=>{
|
|
|
118
124
|
throw new BridgePeerError("GRANT_FAILED");
|
|
119
125
|
}
|
|
120
126
|
};
|
|
127
|
+
const equalBytes = (left, right)=>left.length === right.length && left.every((value, index)=>value === right[index]);
|
|
121
128
|
class RelayConnection {
|
|
122
129
|
#input;
|
|
123
130
|
#channel;
|
|
@@ -136,6 +143,11 @@ class RelayConnection {
|
|
|
136
143
|
#activeIncomingReaders = new Set();
|
|
137
144
|
#activeResponders = new Set();
|
|
138
145
|
#exposureReplacements = new Map();
|
|
146
|
+
#idleUnarySessions = createUnarySessionCache({
|
|
147
|
+
capacity: maxIdleUnarySessions,
|
|
148
|
+
idleMs: unarySessionIdleMs,
|
|
149
|
+
close: (session)=>session.machine.close()
|
|
150
|
+
});
|
|
139
151
|
constructor(input){
|
|
140
152
|
this.#input = input.request;
|
|
141
153
|
this.#channel = input.channel;
|
|
@@ -181,39 +193,49 @@ class RelayConnection {
|
|
|
181
193
|
]));
|
|
182
194
|
}
|
|
183
195
|
async invokeUnary(input) {
|
|
184
|
-
const
|
|
185
|
-
|
|
196
|
+
const sessionKey = `${input.targetPeer}\u0000${input.capability}`;
|
|
197
|
+
for(let attempt = 0; attempt < 2; attempt += 1){
|
|
186
198
|
const invocationId = newId("inv");
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
let dispatched = false;
|
|
190
|
-
const cancel = ()=>{
|
|
191
|
-
if (dispatched) void this.#sendCancel(session, header).catch(()=>undefined);
|
|
192
|
-
};
|
|
193
|
-
input.signal?.addEventListener("abort", cancel, {
|
|
194
|
-
once: true
|
|
195
|
-
});
|
|
199
|
+
const session = this.#idleUnarySessions.take(sessionKey) ?? await this.#openSession(input, false, undefined, input.signal, true);
|
|
200
|
+
let reusable = false;
|
|
196
201
|
try {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
const header = this.#startHeader(session.context, invocationId, input, false);
|
|
203
|
+
const ciphertext = encodeBase64url(session.machine.encrypt(buildInvocationAad(header), input.payload));
|
|
204
|
+
let dispatched = false;
|
|
205
|
+
const cancel = ()=>{
|
|
206
|
+
if (dispatched) void this.#sendCancel(session, header).catch(()=>undefined);
|
|
207
|
+
};
|
|
208
|
+
input.signal?.addEventListener("abort", cancel, {
|
|
209
|
+
once: true
|
|
201
210
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
try {
|
|
212
|
+
assertNotAborted(input.signal);
|
|
213
|
+
this.#channel.send({
|
|
214
|
+
...header,
|
|
215
|
+
ciphertext
|
|
216
|
+
});
|
|
217
|
+
dispatched = true;
|
|
218
|
+
if (input.signal?.aborted) cancel();
|
|
219
|
+
const terminal = await this.#channel.waitFor((frame)=>frame.type === "invoke.result" && frame.invocationId === invocationId || frame.type === "relay.error" && frame.error.correlationId === invocationId, remaining(input.deadlineAt), input.signal);
|
|
220
|
+
if (terminal.type === "relay.error") {
|
|
221
|
+
if (attempt === 0 && terminal.error.bridgeCode === "CONNECTION_LOST" && terminal.error.retryable) continue;
|
|
222
|
+
throw relayFailure(terminal);
|
|
223
|
+
}
|
|
224
|
+
const plaintext = session.machine.decrypt(buildInvocationAad(terminal), decodeBase64url(terminal.ciphertext));
|
|
225
|
+
reusable = session.reusable;
|
|
226
|
+
if (terminal.outcome !== "succeeded") {
|
|
227
|
+
throw new BridgePeerError(terminal.bridgeCode);
|
|
228
|
+
}
|
|
229
|
+
return plaintext;
|
|
230
|
+
} finally{
|
|
231
|
+
input.signal?.removeEventListener("abort", cancel);
|
|
209
232
|
}
|
|
210
|
-
return plaintext;
|
|
211
233
|
} finally{
|
|
212
|
-
|
|
234
|
+
if (reusable && !this.#closed) this.#idleUnarySessions.put(sessionKey, session);
|
|
235
|
+
else session.machine.close();
|
|
213
236
|
}
|
|
214
|
-
} finally{
|
|
215
|
-
session.machine.close();
|
|
216
237
|
}
|
|
238
|
+
throw new BridgePeerError("CONNECTION_LOST", true);
|
|
217
239
|
}
|
|
218
240
|
async invokeStream(input) {
|
|
219
241
|
const session = await this.#openSession(input, true, undefined, input.signal);
|
|
@@ -434,13 +456,14 @@ class RelayConnection {
|
|
|
434
456
|
for (const timer of this.#exposureReplacements.values())clearTimeout(timer);
|
|
435
457
|
this.#exposureReplacements.clear();
|
|
436
458
|
this.#releaseIncoming();
|
|
459
|
+
this.#idleUnarySessions.clear();
|
|
437
460
|
this.#exposures.clear();
|
|
438
461
|
this.#channel.close();
|
|
439
462
|
return Promise.resolve();
|
|
440
463
|
}
|
|
441
|
-
async #openSession(input, stream, expectedTargetEpoch, signal) {
|
|
464
|
+
async #openSession(input, stream, expectedTargetEpoch, signal, requestUnaryReuse = false) {
|
|
442
465
|
assertNotAborted(signal);
|
|
443
|
-
const sessionId = newId("ses");
|
|
466
|
+
const sessionId = newId(requestUnaryReuse ? "sesr1" : "ses");
|
|
444
467
|
this.#channel.send({
|
|
445
468
|
version: "json.v1",
|
|
446
469
|
type: "session.open",
|
|
@@ -474,11 +497,13 @@ class RelayConnection {
|
|
|
474
497
|
message: encodeBase64url(machine.beginHandshake())
|
|
475
498
|
});
|
|
476
499
|
const second = await this.#channel.waitFor((frame)=>frame.type === "session.handshake" && frame.step === 2 && frame.context.sessionId === sessionId, remaining(input.deadlineAt), signal);
|
|
477
|
-
|
|
500
|
+
const handshake = decodeBase64url(second.message);
|
|
501
|
+
const negotiatedPayload = machine.finishHandshakeWithPayload === undefined ? (machine.finishHandshake(handshake), undefined) : machine.finishHandshakeWithPayload(handshake);
|
|
478
502
|
assertNotAborted(signal);
|
|
479
503
|
return Object.freeze({
|
|
480
504
|
context: ready.context,
|
|
481
|
-
machine
|
|
505
|
+
machine,
|
|
506
|
+
reusable: requestUnaryReuse && negotiatedPayload !== undefined && equalBytes(negotiatedPayload, unaryReuseHandshakePayload)
|
|
482
507
|
});
|
|
483
508
|
} catch (error) {
|
|
484
509
|
machine.close();
|
|
@@ -573,7 +598,9 @@ class RelayConnection {
|
|
|
573
598
|
this.#activeResponders.add(machine);
|
|
574
599
|
try {
|
|
575
600
|
const first = await this.#channel.waitFor((frame)=>frame.type === "session.handshake" && frame.step === 1 && frame.context.sessionId === incoming.context.sessionId, this.#timeoutMs);
|
|
576
|
-
const
|
|
601
|
+
const reuseRequested = incoming.context.sessionId.startsWith(unaryReuseSessionPrefix) && machine.acceptHandshakeWithPayload !== undefined;
|
|
602
|
+
const firstMessage = decodeBase64url(first.message);
|
|
603
|
+
const second = reuseRequested ? machine.acceptHandshakeWithPayload(firstMessage, unaryReuseHandshakePayload) : machine.acceptHandshake(firstMessage);
|
|
577
604
|
this.#channel.send({
|
|
578
605
|
version: "json.v1",
|
|
579
606
|
type: "session.handshake",
|
|
@@ -583,10 +610,19 @@ class RelayConnection {
|
|
|
583
610
|
step: 2,
|
|
584
611
|
message: encodeBase64url(second)
|
|
585
612
|
});
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
613
|
+
while(!this.#closed){
|
|
614
|
+
const start = await this.#channel.waitFor((frame)=>frame.type === "invoke.start" && frame.sessionId === incoming.context.sessionId, unaryResponderIdleMs);
|
|
615
|
+
const currentExposure = this.#exposures.get(incoming.capability);
|
|
616
|
+
if (currentExposure === undefined || currentExposure.stream !== start.stream) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (start.stream) {
|
|
620
|
+
await this.#handleIncomingStream(machine, currentExposure, start);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
await this.#handleIncomingUnary(machine, currentExposure, start);
|
|
624
|
+
if (start.fanoutId !== undefined || !reuseRequested) return;
|
|
625
|
+
}
|
|
590
626
|
} finally{
|
|
591
627
|
this.#activeResponders.delete(machine);
|
|
592
628
|
machine.close();
|
|
@@ -831,6 +867,7 @@ class RelayConnection {
|
|
|
831
867
|
if (this.#heartbeatTimer !== undefined) clearTimeout(this.#heartbeatTimer);
|
|
832
868
|
if (this.#renewalTimer !== undefined) clearTimeout(this.#renewalTimer);
|
|
833
869
|
this.#releaseIncoming();
|
|
870
|
+
this.#idleUnarySessions.clear();
|
|
834
871
|
this.#input.onDisconnect(new BridgePeerError("CONNECTION_LOST", true));
|
|
835
872
|
}
|
|
836
873
|
#releaseIncoming() {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** One exclusive idle-value cache with bounded capacity and expiry. */ /** Creates a cache that grants one caller exclusive ownership of each stored value. */ export const createUnarySessionCache = (options)=>{
|
|
2
|
+
if (!Number.isSafeInteger(options.capacity) || options.capacity <= 0) {
|
|
3
|
+
throw new Error("invalid_unary_session_capacity");
|
|
4
|
+
}
|
|
5
|
+
if (!Number.isSafeInteger(options.idleMs) || options.idleMs <= 0) {
|
|
6
|
+
throw new Error("invalid_unary_session_idle_ms");
|
|
7
|
+
}
|
|
8
|
+
const entries = new Map();
|
|
9
|
+
let closed = false;
|
|
10
|
+
const remove = (key, expected)=>{
|
|
11
|
+
const entry = entries.get(key);
|
|
12
|
+
if (entry === undefined || expected !== undefined && entry !== expected) return;
|
|
13
|
+
entries.delete(key);
|
|
14
|
+
clearTimeout(entry.timer);
|
|
15
|
+
options.close(entry.value);
|
|
16
|
+
};
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
take: (key)=>{
|
|
19
|
+
const entry = entries.get(key);
|
|
20
|
+
if (entry === undefined) return undefined;
|
|
21
|
+
entries.delete(key);
|
|
22
|
+
clearTimeout(entry.timer);
|
|
23
|
+
return entry.value;
|
|
24
|
+
},
|
|
25
|
+
put: (key, value)=>{
|
|
26
|
+
if (closed) {
|
|
27
|
+
options.close(value);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
remove(key);
|
|
31
|
+
while(entries.size >= options.capacity){
|
|
32
|
+
remove(entries.keys().next().value);
|
|
33
|
+
}
|
|
34
|
+
const entry = {
|
|
35
|
+
value,
|
|
36
|
+
timer: setTimeout(()=>remove(key, entry), options.idleMs)
|
|
37
|
+
};
|
|
38
|
+
entries.set(key, entry);
|
|
39
|
+
},
|
|
40
|
+
clear: ()=>{
|
|
41
|
+
if (closed) return;
|
|
42
|
+
closed = true;
|
|
43
|
+
for (const key of [
|
|
44
|
+
...entries.keys()
|
|
45
|
+
])remove(key);
|
|
46
|
+
},
|
|
47
|
+
get size () {
|
|
48
|
+
return entries.size;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|