@sanctumterra/raknet 1.3.75 → 1.3.76
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/client/framer.d.ts +5 -1
- package/dist/client/framer.d.ts.map +1 -1
- package/dist/client/framer.js +74 -81
- package/package.json +1 -1
package/dist/client/framer.d.ts
CHANGED
|
@@ -29,10 +29,14 @@ export declare class Framer {
|
|
|
29
29
|
outputBackup: Map<number, Frame[]>;
|
|
30
30
|
_tickCount: number;
|
|
31
31
|
private mtuDiff;
|
|
32
|
+
private readonly BATCH_SIZE;
|
|
33
|
+
private readonly MAX_BATCH_INTERVAL;
|
|
34
|
+
private lastBatchTime;
|
|
32
35
|
private readonly ORDERING_QUEUE_TIMEOUT;
|
|
33
|
-
private readonly FRAGMENT_TIMEOUT;
|
|
34
36
|
constructor(client: Client);
|
|
35
37
|
tick(): void;
|
|
38
|
+
private processBatchedAcksAndNacks;
|
|
39
|
+
private processBatchedOrderedFrames;
|
|
36
40
|
incommingMessage(payload: Buffer, rinfo: RemoteInfo): void;
|
|
37
41
|
private processFrame;
|
|
38
42
|
handle(frameSet: Frameset): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"framer.d.ts","sourceRoot":"","sources":["../../src/client/framer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,KAAK,EAGL,QAAQ,
|
|
1
|
+
{"version":3,"file":"framer.d.ts","sourceRoot":"","sources":["../../src/client/framer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,KAAK,EAGL,QAAQ,EAUR,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,UAAU,WAAW;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,MAAM;IAClB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,sBAAsB,CAA0B;IACxD,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,yBAAyB,CAAmC;IACpE,OAAO,CAAC,eAAe,CAAmC;IAC1D,SAAS,CAAC,kBAAkB,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAG3D;IACF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,GAAG,CACrC,MAAM,EACN;QAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CACpD,CAAa;IAEP,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,QAAQ,CAAC;IAClC,SAAS,CAAC,cAAc,SAAK;IAC7B,SAAS,CAAC,gBAAgB,SAAK;IAC/B,SAAS,CAAC,mBAAmB,SAAK;IAC3B,YAAY,EAAE,KAAK,EAAE,CAAM;IAClC,OAAO,CAAC,sBAAsB,CAAK;IAC5B,YAAY,uBAA8B;IAC1C,UAAU,SAAK;IAEtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAM;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAM;IACzC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAO;gBAElC,MAAM,EAAE,MAAM;IASnB,IAAI;IAmCX,OAAO,CAAC,0BAA0B;IAoBlC,OAAO,CAAC,2BAA2B;IAiC5B,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU;IAoE1D,OAAO,CAAC,YAAY;IA6Eb,MAAM,CAAC,QAAQ,EAAE,QAAQ;IA0DhC,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,oBAAoB;IAqB5B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,4BAA4B;IA2B7B,YAAY,CAClB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,QAA0B,GAClC,IAAI;IAOA,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA+BxD,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,UAAU;IAqBX,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CA6BtC"}
|
package/dist/client/framer.js
CHANGED
|
@@ -26,8 +26,10 @@ class Framer {
|
|
|
26
26
|
outputBackup = new Map();
|
|
27
27
|
_tickCount = 0;
|
|
28
28
|
mtuDiff;
|
|
29
|
+
BATCH_SIZE = 32; // Maximum frames to process in a batch
|
|
30
|
+
MAX_BATCH_INTERVAL = 50; // Maximum time (ms) to wait before processing a batch
|
|
31
|
+
lastBatchTime = 0;
|
|
29
32
|
ORDERING_QUEUE_TIMEOUT = 500;
|
|
30
|
-
FRAGMENT_TIMEOUT = 5000;
|
|
31
33
|
constructor(client) {
|
|
32
34
|
this.client = client;
|
|
33
35
|
this.outputFrameQueue = new proto_2.Frameset();
|
|
@@ -38,82 +40,84 @@ class Framer {
|
|
|
38
40
|
}
|
|
39
41
|
tick() {
|
|
40
42
|
this._tickCount++;
|
|
43
|
+
const now = Date.now();
|
|
41
44
|
if (this.client.status === proto_1.Status.Disconnected ||
|
|
42
45
|
this.client.status === proto_1.Status.Disconnecting) {
|
|
43
|
-
if (this.client.options.debug) {
|
|
44
|
-
utils_1.Logger.debug(`[Framer] Skipping tick - client status: ${proto_1.Status[this.client.status]}`);
|
|
45
|
-
}
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
// Send a ping every 50 ticks.
|
|
49
49
|
if (this._tickCount % 50 === 0) {
|
|
50
|
-
const now = Date.now();
|
|
51
50
|
const ping = new proto_1.ConnectedPing();
|
|
52
51
|
ping.timestamp = BigInt(now);
|
|
53
52
|
this.frameAndSend(ping.serialize(), proto_1.Priority.Immediate);
|
|
54
53
|
}
|
|
55
|
-
//
|
|
54
|
+
// Batch process ACKs and NACKs
|
|
55
|
+
this.processBatchedAcksAndNacks();
|
|
56
|
+
// Process ordered frames in batches
|
|
57
|
+
this.processBatchedOrderedFrames(now);
|
|
58
|
+
// Send queued frames if we have enough or enough time has passed
|
|
59
|
+
if (this.outputFrames.length >= this.BATCH_SIZE ||
|
|
60
|
+
(this.outputFrames.length > 0 &&
|
|
61
|
+
now - this.lastBatchTime >= this.MAX_BATCH_INTERVAL)) {
|
|
62
|
+
this.sendQueue(this.outputFrames.length);
|
|
63
|
+
this.lastBatchTime = now;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
processBatchedAcksAndNacks() {
|
|
67
|
+
// Batch process ACKs
|
|
56
68
|
if (this.receivedFrameSequences.size > 0) {
|
|
57
69
|
const ackSeqs = Array.from(this.receivedFrameSequences);
|
|
58
70
|
this.receivedFrameSequences.clear();
|
|
59
|
-
if (this.client.options.debug) {
|
|
60
|
-
utils_1.Logger.debug(`[Framer] Sending ACK for ${ackSeqs.length} sequences`);
|
|
61
|
-
}
|
|
62
71
|
const ack = new proto_1.Ack();
|
|
63
72
|
ack.sequences = ackSeqs;
|
|
64
73
|
this.client.send(ack.serialize());
|
|
65
74
|
}
|
|
66
|
-
//
|
|
75
|
+
// Batch process NACKs
|
|
67
76
|
if (this.lostFrameSequences.size > 0) {
|
|
68
77
|
const nackSeqs = Array.from(this.lostFrameSequences);
|
|
69
78
|
this.lostFrameSequences.clear();
|
|
70
|
-
if (this.client.options.debug) {
|
|
71
|
-
utils_1.Logger.debug(`[Framer] Sending NACK for ${nackSeqs.length} sequences`);
|
|
72
|
-
}
|
|
73
79
|
const nack = new proto_1.Nack();
|
|
74
80
|
nack.sequences = nackSeqs;
|
|
75
81
|
this.client.send(nack.serialize());
|
|
76
82
|
}
|
|
77
|
-
|
|
83
|
+
}
|
|
84
|
+
processBatchedOrderedFrames(now) {
|
|
85
|
+
const processedChannels = new Set();
|
|
78
86
|
for (let channel = 0; channel < this.inputOrderingQueue.length; channel++) {
|
|
87
|
+
if (processedChannels.has(channel))
|
|
88
|
+
continue;
|
|
79
89
|
const queue = this.inputOrderingQueue[channel];
|
|
90
|
+
let processed = 0;
|
|
80
91
|
let expected = this.inputOrderIndex[channel];
|
|
81
|
-
|
|
82
|
-
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
|
92
|
+
while (queue.has(expected) && processed < this.BATCH_SIZE) {
|
|
83
93
|
const queued = queue.get(expected);
|
|
94
|
+
if (!queued) {
|
|
95
|
+
expected++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
84
98
|
if (now - queued.timestamp > this.ORDERING_QUEUE_TIMEOUT) {
|
|
85
|
-
utils_1.Logger.warn(`[Framer] Timeout waiting for ordered frame ${expected} on channel ${channel}; skipping it`);
|
|
86
99
|
this.processFrame(queued.frame);
|
|
87
100
|
queue.delete(expected);
|
|
88
101
|
expected++;
|
|
89
|
-
|
|
102
|
+
processed++;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
break;
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
utils_1.Logger.warn(`[Framer] Timeout reassembling fragments for splitId=${splitId}; discarding incomplete fragments`);
|
|
96
|
-
this.fragmentsQueue.delete(splitId);
|
|
108
|
+
if (processed > 0) {
|
|
109
|
+
this.inputOrderIndex[channel] = expected;
|
|
110
|
+
processedChannels.add(channel);
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
|
-
if (this.client.options.debug) {
|
|
100
|
-
utils_1.Logger.debug(`[Framer] Queue state - Frames: ${this.outputFrames.length}, Backup: ${this.outputBackup.size}`);
|
|
101
|
-
}
|
|
102
|
-
this.sendQueue(this.outputFrames.length);
|
|
103
113
|
}
|
|
104
114
|
incommingMessage(payload, rinfo) {
|
|
105
115
|
let header = payload.readUint8();
|
|
106
116
|
if ((header & 0xf0) === 0x80)
|
|
107
117
|
header = 0x80;
|
|
108
|
-
if (this.client.options.debug) {
|
|
109
|
-
utils_1.Logger.debug(`[Framer] Received packet ${header} from ${rinfo.address}:${rinfo.port}`);
|
|
110
|
-
}
|
|
111
118
|
switch (header) {
|
|
112
119
|
case proto_1.Packet.Ack: {
|
|
113
120
|
const ack = new proto_1.Ack(payload).deserialize();
|
|
114
|
-
if (this.client.options.debug) {
|
|
115
|
-
utils_1.Logger.debug(`[Framer] Processing ACK with ${ack.sequences.length} sequences`);
|
|
116
|
-
}
|
|
117
121
|
for (let i = 0, len = ack.sequences.length; i < len; i++) {
|
|
118
122
|
this.outputBackup.delete(ack.sequences[i]);
|
|
119
123
|
}
|
|
@@ -121,15 +125,9 @@ class Framer {
|
|
|
121
125
|
}
|
|
122
126
|
case proto_1.Packet.Nack: {
|
|
123
127
|
const nack = new proto_1.Nack(payload).deserialize();
|
|
124
|
-
if (this.client.options.debug) {
|
|
125
|
-
utils_1.Logger.debug(`[Framer] Processing NACK with ${nack.sequences.length} sequences`);
|
|
126
|
-
}
|
|
127
128
|
for (let i = 0, len = nack.sequences.length; i < len; i++) {
|
|
128
129
|
const seq = nack.sequences[i];
|
|
129
130
|
const lostFrames = this.outputBackup.get(seq) || [];
|
|
130
|
-
if (this.client.options.debug) {
|
|
131
|
-
utils_1.Logger.debug(`[Framer] Resending ${lostFrames.length} lost frames for sequence ${seq}`);
|
|
132
|
-
}
|
|
133
131
|
for (let j = 0, lostLen = lostFrames.length; j < lostLen; j++) {
|
|
134
132
|
this.sendFrame(lostFrames[j], proto_1.Priority.Immediate);
|
|
135
133
|
}
|
|
@@ -157,7 +155,6 @@ class Framer {
|
|
|
157
155
|
const packet = new proto_1.OpenConnectionReplyTwo(payload).deserialize();
|
|
158
156
|
this.client.emit("open-connection-reply-two", packet);
|
|
159
157
|
this.client.options.mtuSize = packet.mtu;
|
|
160
|
-
// this.mtuDiff = this.client.options.mtuSize - 36;
|
|
161
158
|
const conReq = new proto_1.ConnectionRequest();
|
|
162
159
|
conReq.clientGuid = this.client.options.clientId;
|
|
163
160
|
conReq.timestamp = BigInt(Date.now());
|
|
@@ -269,12 +266,17 @@ class Framer {
|
|
|
269
266
|
}
|
|
270
267
|
}
|
|
271
268
|
this.lastInputSequence = frameSet.sequence;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
269
|
+
// Process frames in batches
|
|
270
|
+
const frames = frameSet.frames;
|
|
271
|
+
for (let i = 0; i < frames.length; i += this.BATCH_SIZE) {
|
|
272
|
+
const batch = frames.slice(i, i + this.BATCH_SIZE);
|
|
273
|
+
for (const frame of batch) {
|
|
274
|
+
try {
|
|
275
|
+
this.handleFrame(frame);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
utils_1.Logger.error("[Framer] Error handling frame", err);
|
|
279
|
+
}
|
|
278
280
|
}
|
|
279
281
|
}
|
|
280
282
|
}
|
|
@@ -283,9 +285,6 @@ class Framer {
|
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
handleFrame(frame) {
|
|
286
|
-
if (this.client.options.debug) {
|
|
287
|
-
utils_1.Logger.debug(`[Framer] Handling frame - Split: ${frame.isSplit}, Sequenced: ${frame.isSequenced}, Ordered: ${frame.isOrdered}`);
|
|
288
|
-
}
|
|
289
288
|
if (frame.isSplit) {
|
|
290
289
|
this.handleSplit(frame);
|
|
291
290
|
}
|
|
@@ -306,19 +305,11 @@ class Framer {
|
|
|
306
305
|
this.processOrderedFrames(frame);
|
|
307
306
|
}
|
|
308
307
|
else if (frame.orderedFrameIndex > expectedOrderIndex) {
|
|
309
|
-
if (this.client.options.debug) {
|
|
310
|
-
utils_1.Logger.debug(`Queuing out-of-order frame: ${frame.orderedFrameIndex}`);
|
|
311
|
-
}
|
|
312
308
|
this.inputOrderingQueue[channel].set(frame.orderedFrameIndex, {
|
|
313
309
|
frame,
|
|
314
310
|
timestamp: Date.now(),
|
|
315
311
|
});
|
|
316
312
|
}
|
|
317
|
-
else {
|
|
318
|
-
if (this.client.options.debug) {
|
|
319
|
-
utils_1.Logger.debug(`Discarding old frame: ${frame.orderedFrameIndex}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
313
|
}
|
|
323
314
|
processOrderedFrames(frame) {
|
|
324
315
|
const channel = frame.orderChannel;
|
|
@@ -328,8 +319,11 @@ class Framer {
|
|
|
328
319
|
const queue = this.inputOrderingQueue[channel];
|
|
329
320
|
let nextOrderIndex = this.inputOrderIndex[channel];
|
|
330
321
|
while (queue.has(nextOrderIndex)) {
|
|
331
|
-
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
|
332
322
|
const queued = queue.get(nextOrderIndex);
|
|
323
|
+
if (!queued) {
|
|
324
|
+
nextOrderIndex++;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
333
327
|
this.processFrame(queued.frame);
|
|
334
328
|
queue.delete(nextOrderIndex);
|
|
335
329
|
nextOrderIndex++;
|
|
@@ -339,14 +333,8 @@ class Framer {
|
|
|
339
333
|
handleSequenced(frame) {
|
|
340
334
|
const channel = frame.orderChannel;
|
|
341
335
|
const currentHighestSequence = this.inputHighestSequenceIndex[channel];
|
|
342
|
-
if (this.client.options.debug) {
|
|
343
|
-
utils_1.Logger.debug(`Handling sequenced frame: sequenceFrameIndex=${frame.sequenceFrameIndex}, currentHighest=${currentHighestSequence}`);
|
|
344
|
-
}
|
|
345
336
|
if (frame.sequenceFrameIndex < currentHighestSequence ||
|
|
346
337
|
frame.orderedFrameIndex === this.inputOrderIndex[channel]) {
|
|
347
|
-
if (this.client.options.debug) {
|
|
348
|
-
utils_1.Logger.debug(`Discarding old sequenced frame: ${frame.sequenceFrameIndex}`);
|
|
349
|
-
}
|
|
350
338
|
return;
|
|
351
339
|
}
|
|
352
340
|
this.inputHighestSequenceIndex[channel] = frame.sequenceFrameIndex + 1;
|
|
@@ -362,19 +350,18 @@ class Framer {
|
|
|
362
350
|
entry.fragments.set(frame.splitFrameIndex, frame);
|
|
363
351
|
if (entry.fragments.size === frame.splitCount) {
|
|
364
352
|
this.reassembleAndProcessFragment(frame, entry.fragments);
|
|
353
|
+
this.fragmentsQueue.delete(splitId);
|
|
365
354
|
}
|
|
366
355
|
}
|
|
367
356
|
reassembleAndProcessFragment(frame, fragment) {
|
|
368
357
|
const stream = new binarystream_1.BinaryStream();
|
|
369
358
|
for (let index = 0; index < frame.splitCount; index++) {
|
|
370
359
|
const sframe = fragment.get(index);
|
|
371
|
-
if (sframe) {
|
|
372
|
-
stream.writeBuffer(sframe.payload);
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
360
|
+
if (!sframe) {
|
|
375
361
|
utils_1.Logger.error(`Missing fragment at index ${index} for splitId=${frame.splitId}`);
|
|
376
362
|
return;
|
|
377
363
|
}
|
|
364
|
+
stream.writeBuffer(sframe.payload);
|
|
378
365
|
}
|
|
379
366
|
const reassembledFrame = new proto_1.Frame();
|
|
380
367
|
reassembledFrame.reliability = frame.reliability;
|
|
@@ -383,7 +370,6 @@ class Framer {
|
|
|
383
370
|
reassembledFrame.orderedFrameIndex = frame.orderedFrameIndex;
|
|
384
371
|
reassembledFrame.orderChannel = frame.orderChannel;
|
|
385
372
|
reassembledFrame.payload = stream.getBuffer();
|
|
386
|
-
this.fragmentsQueue.delete(frame.splitId);
|
|
387
373
|
this.handleFrame(reassembledFrame);
|
|
388
374
|
}
|
|
389
375
|
frameAndSend(payload, priority = proto_1.Priority.Normal) {
|
|
@@ -436,32 +422,39 @@ class Framer {
|
|
|
436
422
|
queueFrame(frame, priority) {
|
|
437
423
|
const frameLength = frame.getByteLength();
|
|
438
424
|
const totalLength = 4 + this.outputFramesByteLength + frameLength;
|
|
439
|
-
if (totalLength > this.mtuDiff
|
|
425
|
+
if (totalLength > this.mtuDiff ||
|
|
426
|
+
this.outputFrames.length >= this.BATCH_SIZE) {
|
|
440
427
|
this.sendQueue(this.outputFrames.length);
|
|
428
|
+
this.lastBatchTime = Date.now();
|
|
441
429
|
}
|
|
442
430
|
this.outputFrames.push(frame);
|
|
443
431
|
this.outputFramesByteLength += frameLength;
|
|
444
432
|
if (priority === proto_1.Priority.Immediate) {
|
|
445
433
|
this.sendQueue(1);
|
|
434
|
+
this.lastBatchTime = Date.now();
|
|
446
435
|
}
|
|
447
436
|
}
|
|
448
437
|
sendQueue(amount) {
|
|
449
438
|
if (this.outputFrames.length === 0)
|
|
450
439
|
return;
|
|
451
|
-
if (this.client.options.debug) {
|
|
452
|
-
utils_1.Logger.debug(`[Framer] Sending queue with ${amount} frames, total queued: ${this.outputFrames.length}`);
|
|
453
|
-
}
|
|
454
440
|
const frameset = new proto_2.Frameset();
|
|
455
441
|
frameset.sequence = this.outputSequence++;
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
442
|
+
// Send in batches if amount is larger than batch size
|
|
443
|
+
const remainingFrames = [...this.outputFrames];
|
|
444
|
+
this.outputFrames = [];
|
|
445
|
+
while (remainingFrames.length > 0) {
|
|
446
|
+
const batchSize = Math.min(this.BATCH_SIZE, remainingFrames.length);
|
|
447
|
+
const framesToSend = remainingFrames.splice(0, batchSize);
|
|
448
|
+
frameset.frames = framesToSend;
|
|
449
|
+
const sentLength = framesToSend.reduce((sum, f) => sum + f.getByteLength(), 0);
|
|
450
|
+
this.outputFramesByteLength -= sentLength;
|
|
451
|
+
this.outputBackup.set(frameset.sequence, framesToSend);
|
|
452
|
+
this.client.send(frameset.serialize());
|
|
453
|
+
// Increment sequence for next batch if there are more frames
|
|
454
|
+
if (remainingFrames.length > 0) {
|
|
455
|
+
frameset.sequence = this.outputSequence++;
|
|
456
|
+
}
|
|
463
457
|
}
|
|
464
|
-
this.client.send(frameset.serialize());
|
|
465
458
|
}
|
|
466
459
|
}
|
|
467
460
|
exports.Framer = Framer;
|