@cliz/inlets
Version:
Cloud Native Tunnel
178 lines (177 loc) • 5.92 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamManager = exports.Stream = exports.StreamState = void 0;
var StreamState;
(function (StreamState) {
StreamState["INITIALIZING"] = "initializing";
StreamState["ACTIVE"] = "active";
StreamState["PAUSED"] = "paused";
StreamState["COMPLETED"] = "completed";
StreamState["ERROR"] = "error";
})(StreamState = exports.StreamState || (exports.StreamState = {}));
class Stream {
constructor(id) {
this.state = StreamState.INITIALIZING;
this.chunks = new Map();
this.expectedSequence = 0;
this.totalChunks = -1;
this.id = id;
}
addChunk(sequence, data, isLast = false) {
if (this.state === StreamState.COMPLETED || this.state === StreamState.ERROR) {
return;
}
this.chunks.set(sequence, { sequence, data, isLast });
if (isLast) {
this.totalChunks = sequence + 1;
}
this.tryReassemble();
}
tryReassemble() {
while (this.chunks.has(this.expectedSequence)) {
const chunk = this.chunks.get(this.expectedSequence);
this.chunks.delete(this.expectedSequence);
if (!this._completedData) {
this._completedData = chunk.data;
}
else {
const previous = this._completedData;
const mergedArray = new Uint8Array(previous.length + chunk.data.length);
mergedArray.set(previous, 0);
mergedArray.set(chunk.data, previous.length);
this._completedData = Buffer.from(mergedArray);
}
this.completedData = this._completedData;
this.expectedSequence++;
if (chunk.isLast) {
this.state = StreamState.COMPLETED;
if (this.onComplete && this._completedData) {
this.onComplete(this._completedData);
}
return;
}
if (this.totalChunks > 0 && this.expectedSequence >= this.totalChunks) {
this.state = StreamState.COMPLETED;
if (this.onComplete && this._completedData) {
this.onComplete(this._completedData);
}
return;
}
}
}
markComplete() {
if (this.state === StreamState.COMPLETED || this.state === StreamState.ERROR) {
return;
}
this.state = StreamState.COMPLETED;
if (this.onComplete && this._completedData) {
this.onComplete(this._completedData);
}
}
markError(error) {
this.state = StreamState.ERROR;
if (this.onError) {
this.onError(error);
}
}
pause() {
if (this.state === StreamState.ACTIVE) {
this.state = StreamState.PAUSED;
}
}
resume() {
if (this.state === StreamState.PAUSED) {
this.state = StreamState.ACTIVE;
this.tryReassemble();
}
}
getPartialData() {
return this._completedData || null;
}
destroy() {
this.chunks.clear();
this._completedData = undefined;
this.completedData = undefined;
this.onComplete = undefined;
this.onError = undefined;
}
}
exports.Stream = Stream;
class StreamManager {
constructor(defaultChunkSize) {
this.streams = new Map();
this.defaultChunkSize = 64 * 1024;
this.maxStreamAge = 5 * 60 * 1000;
this.cleanupInterval = null;
if (defaultChunkSize) {
this.defaultChunkSize = defaultChunkSize;
}
this.startCleanupTimer();
}
splitIntoChunks(data, chunkSize) {
const size = chunkSize || this.defaultChunkSize;
const chunks = [];
for (let i = 0; i < data.length; i += size) {
const chunk = data.slice(i, Math.min(i + size, data.length));
chunks.push(chunk);
}
return chunks;
}
createStream(streamId, onComplete, onError) {
const stream = new Stream(streamId);
stream.state = StreamState.ACTIVE;
stream.onComplete = onComplete;
stream.onError = onError;
this.streams.set(streamId, stream);
return stream;
}
getStream(streamId) {
return this.streams.get(streamId);
}
addChunk(streamId, sequence, data, isLast = false) {
const stream = this.getStream(streamId);
if (!stream) {
const newStream = this.createStream(streamId);
newStream.addChunk(sequence, data, isLast);
}
else {
stream.addChunk(sequence, data, isLast);
}
}
removeStream(streamId) {
const stream = this.streams.get(streamId);
if (stream) {
stream.destroy();
this.streams.delete(streamId);
}
}
startCleanupTimer() {
this.cleanupInterval = setInterval(() => {
const now = Date.now();
const streamsToRemove = [];
for (const [streamId, stream] of this.streams.entries()) {
if ((stream.state === StreamState.COMPLETED || stream.state === StreamState.ERROR) &&
stream.completedData) {
streamsToRemove.push(streamId);
}
}
for (const streamId of streamsToRemove) {
this.removeStream(streamId);
}
}, 60 * 1000);
}
stopCleanupTimer() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
}
destroy() {
this.stopCleanupTimer();
for (const stream of this.streams.values()) {
stream.destroy();
}
this.streams.clear();
}
}
exports.StreamManager = StreamManager;