@based/client
Advanced tools
Comparing version 5.3.0 to 6.0.0
@@ -44,6 +44,8 @@ class Emitter { | ||
} | ||
const listener = (v) => { | ||
fn(v); | ||
// TODO: optmize this | ||
const listener = () => { | ||
this.off(type, listener); | ||
this.off(type, fn); | ||
}; | ||
this.on(type, fn); | ||
this.on(type, listener); | ||
@@ -61,3 +63,2 @@ } | ||
listeners.splice(i, 1); | ||
i--; | ||
break; | ||
@@ -64,0 +65,0 @@ } |
@@ -202,2 +202,9 @@ import { inflateSync } from 'fflate'; | ||
} | ||
if (payload.streamRequestId) { | ||
if (client.streamFunctionResponseListeners.has(payload.streamRequestId)) { | ||
const [, reject] = client.streamFunctionResponseListeners.get(payload.streamRequestId); | ||
reject(convertDataToBasedError(payload)); | ||
client.streamFunctionResponseListeners.delete(payload.streamRequestId); | ||
} | ||
} | ||
if (payload.requestId) { | ||
@@ -281,6 +288,7 @@ if (client.functionResponseListeners.has(payload.requestId)) { | ||
} | ||
} // ----------- Channel message | ||
} // ----------- SubType 7 | ||
else if (type === 7) { | ||
// | 4 header | 1 subType | | ||
const subType = readUint8(buffer, 4, 1); | ||
// channel | ||
if (subType === 0) { | ||
@@ -309,2 +317,32 @@ // | 4 header | 1 subType | 8 id | * payload | | ||
} | ||
else if (subType === 1) { | ||
// | 4 header | 1 subType | 3 id | * payload | | ||
const id = readUint8(buffer, 5, 3); | ||
const start = 8; | ||
const end = len + 4; | ||
let payload; | ||
// if not empty response, parse it | ||
if (len !== 4) { | ||
payload = JSON.parse(decodeAndDeflate(start, end, isDeflate, buffer)); | ||
} | ||
if (client.streamFunctionResponseListeners.has(id)) { | ||
client.streamFunctionResponseListeners.get(id)[0](payload); | ||
client.streamFunctionResponseListeners.delete(id); | ||
} | ||
} | ||
else if (subType === 2) { | ||
// | 4 header | 1 subType | 3 id | 1 seqId | 1 code | maxChunkSize | ||
const id = readUint8(buffer, 5, 3); | ||
const seqId = readUint8(buffer, 8, 1); | ||
const code = readUint8(buffer, 9, 1); | ||
let maxChunkSize = 0; | ||
if (len > 10 - 4) { | ||
maxChunkSize = readUint8(buffer, 10, len - 6); | ||
console.log('more data', maxChunkSize); | ||
} | ||
// if len is smaller its an error OR use 0 as error (1 - 255) | ||
if (client.streamFunctionResponseListeners.has(id)) { | ||
client.streamFunctionResponseListeners.get(id)[2](seqId, code, maxChunkSize); | ||
} | ||
} | ||
} | ||
@@ -311,0 +349,0 @@ // --------------------------------- |
@@ -10,5 +10,7 @@ import { addGetToQueue } from '../outgoing/index.js'; | ||
// 4 = authData | ||
// 5 = errorData // TODO: make this 7.0 and channelMessage 5 | ||
// 5 = errorData | ||
// 6 = publish requesChannelName | ||
// 7.0 = channelMessage | ||
// 7.1 = stream reply | ||
// 7.2 = stream chunk reply | ||
// isDeflate (1 bit) | ||
@@ -15,0 +17,0 @@ // len (28 bits) |
@@ -1,8 +0,8 @@ | ||
import { BasedOpts, AuthState, FunctionResponseListeners, Settings, FunctionQueue, ObserveState, ObserveQueue, Cache, GetObserveQueue, GetState, ChannelQueue, ChannelPublishQueue, ChannelState, CallOptions, QueryOptions } from './types/index.js'; | ||
import { Connection } from './websocket/types.js'; | ||
import Emitter from './Emitter.js'; | ||
import { BasedQuery } from './query/index.js'; | ||
import { StreamFunctionOpts } from './stream/types.js'; | ||
import { BasedChannel } from './channel/index.js'; | ||
export * from './authState/parseAuthState.js'; | ||
import { BasedOpts, AuthState, FunctionResponseListeners, Settings, FunctionQueue, ObserveState, ObserveQueue, Cache, GetObserveQueue, GetState, ChannelQueue, ChannelPublishQueue, ChannelState, CallOptions, QueryOptions } from "./types/index.js"; | ||
import { Connection } from "./websocket/types.js"; | ||
import Emitter from "./Emitter.js"; | ||
import { BasedQuery } from "./query/index.js"; | ||
import { StreamFunctionOpts, StreamQueue, StreamFunctionResponseListeners } from "./stream/types.js"; | ||
import { BasedChannel } from "./channel/index.js"; | ||
export * from "./authState/parseAuthState.js"; | ||
export { AuthState, BasedQuery }; | ||
@@ -29,2 +29,3 @@ export declare class BasedClient extends Emitter { | ||
fQ: FunctionQueue; | ||
sQ: StreamQueue; | ||
oQ: ObserveQueue; | ||
@@ -55,2 +56,4 @@ cQ: ChannelQueue; | ||
}; | ||
streamFunctionResponseListeners: StreamFunctionResponseListeners; | ||
streamRequestId: number; | ||
clearUnusedCache(): void; | ||
@@ -148,3 +151,3 @@ onClose(): void; | ||
*/ | ||
stream(name: string, stream: StreamFunctionOpts, progressListener?: (progress: number) => void): Promise<any>; | ||
stream(name: string, stream: StreamFunctionOpts, progressListener?: (progress: number, bytes: number) => void): Promise<any>; | ||
/** | ||
@@ -151,0 +154,0 @@ Set auth state on client and server, `persistent` |
@@ -1,8 +0,8 @@ | ||
import { BasedOpts, AuthState, FunctionResponseListeners, Settings, FunctionQueue, ObserveState, ObserveQueue, Cache, GetObserveQueue, GetState, ChannelQueue, ChannelPublishQueue, ChannelState, CallOptions, QueryOptions } from './types/index.js'; | ||
import { Connection } from './websocket/types.js'; | ||
import Emitter from './Emitter.js'; | ||
import { BasedQuery } from './query/index.js'; | ||
import { StreamFunctionOpts } from './stream/types.js'; | ||
import { BasedChannel } from './channel/index.js'; | ||
export * from './authState/parseAuthState.js'; | ||
import { BasedOpts, AuthState, FunctionResponseListeners, Settings, FunctionQueue, ObserveState, ObserveQueue, Cache, GetObserveQueue, GetState, ChannelQueue, ChannelPublishQueue, ChannelState, CallOptions, QueryOptions } from "./types/index.js"; | ||
import { Connection } from "./websocket/types.js"; | ||
import Emitter from "./Emitter.js"; | ||
import { BasedQuery } from "./query/index.js"; | ||
import { StreamFunctionOpts, StreamQueue, StreamFunctionResponseListeners } from "./stream/types.js"; | ||
import { BasedChannel } from "./channel/index.js"; | ||
export * from "./authState/parseAuthState.js"; | ||
export { AuthState, BasedQuery }; | ||
@@ -29,2 +29,3 @@ export declare class BasedClient extends Emitter { | ||
fQ: FunctionQueue; | ||
sQ: StreamQueue; | ||
oQ: ObserveQueue; | ||
@@ -55,2 +56,4 @@ cQ: ChannelQueue; | ||
}; | ||
streamFunctionResponseListeners: StreamFunctionResponseListeners; | ||
streamRequestId: number; | ||
clearUnusedCache(): void; | ||
@@ -148,3 +151,3 @@ onClose(): void; | ||
*/ | ||
stream(name: string, stream: StreamFunctionOpts, progressListener?: (progress: number) => void): Promise<any>; | ||
stream(name: string, stream: StreamFunctionOpts, progressListener?: (progress: number, bytes: number) => void): Promise<any>; | ||
/** | ||
@@ -151,0 +154,0 @@ Set auth state on client and server, `persistent` |
@@ -1,17 +0,17 @@ | ||
import connectWebsocket from './websocket/index.js'; | ||
import Emitter from './Emitter.js'; | ||
import { addChannelPublishIdentifier, addChannelSubscribeToQueue, addObsToQueue, addToFunctionQueue, drainQueue, sendAuth, } from './outgoing/index.js'; | ||
import { incoming } from './incoming/index.js'; | ||
import { BasedQuery } from './query/index.js'; | ||
import startStream from './stream/index.js'; | ||
import { initStorage, clearStorage, updateStorage, } from './persistentStorage/index.js'; | ||
import { BasedChannel } from './channel/index.js'; | ||
import { hashObjectIgnoreKeyOrder } from '@saulx/hash'; | ||
import { deepEqual } from '@saulx/utils'; | ||
import parseOpts from '@based/opts'; | ||
import { freeCacheMemory } from './cache.js'; | ||
export * from './authState/parseAuthState.js'; | ||
import connectWebsocket from "./websocket/index.js"; | ||
import Emitter from "./Emitter.js"; | ||
import { addChannelPublishIdentifier, addChannelSubscribeToQueue, addObsToQueue, addToFunctionQueue, drainQueue, sendAuth, } from "./outgoing/index.js"; | ||
import { incoming } from "./incoming/index.js"; | ||
import { BasedQuery } from "./query/index.js"; | ||
import startStream from "./stream/index.js"; | ||
import { initStorage, clearStorage, updateStorage, } from "./persistentStorage/index.js"; | ||
import { BasedChannel } from "./channel/index.js"; | ||
import { hashObjectIgnoreKeyOrder } from "@saulx/hash"; | ||
import { deepEqual } from "@saulx/utils"; | ||
import parseOpts from "@based/opts"; | ||
import { freeCacheMemory } from "./cache.js"; | ||
export * from "./authState/parseAuthState.js"; | ||
export { BasedQuery }; | ||
// global polyfill | ||
if (typeof window !== 'undefined' && typeof global === 'undefined') { | ||
if (typeof window !== "undefined" && typeof global === "undefined") { | ||
window.global = window; | ||
@@ -26,3 +26,3 @@ } | ||
if (settings?.maxCacheSize) { | ||
console.warn('MaxCacheSize setting not implemented yet...'); | ||
console.warn("MaxCacheSize setting not implemented yet..."); | ||
this.maxCacheSize = settings.maxCacheSize; | ||
@@ -52,2 +52,3 @@ } | ||
fQ = []; | ||
sQ = []; | ||
oQ = new Map(); | ||
@@ -84,2 +85,5 @@ cQ = new Map(); | ||
}; | ||
// --------- Function State | ||
streamFunctionResponseListeners = new Map(); | ||
streamRequestId = 0; // max 3 bytes (0 to 16777215) | ||
// cache | ||
@@ -106,11 +110,11 @@ clearUnusedCache() { | ||
} | ||
this.emit('disconnect', true); | ||
this.emit("disconnect", true); | ||
} | ||
onReconnect() { | ||
this.connected = true; | ||
this.emit('reconnect', true); | ||
this.emit("reconnect", true); | ||
} | ||
onOpen() { | ||
this.connected = true; | ||
this.emit('connect', true); | ||
this.emit("connect", true); | ||
// Resend all subscriptions | ||
@@ -171,3 +175,3 @@ for (const [id, obs] of this.observeState) { | ||
if (!this.opts) { | ||
console.error('Configure opts to connect'); | ||
console.error("Configure opts to connect"); | ||
return; | ||
@@ -283,3 +287,3 @@ } | ||
retries++; | ||
if (typeof newTime === 'number' && !isNaN(newTime)) { | ||
if (typeof newTime === "number" && !isNaN(newTime)) { | ||
time = newTime; | ||
@@ -326,7 +330,7 @@ if (newTime === 0) { | ||
setAuthState(authState) { | ||
if (typeof authState === 'object') { | ||
if (typeof authState === "object") { | ||
return sendAuth(this, authState); | ||
} | ||
else { | ||
throw new Error('Invalid auth() arguments'); | ||
throw new Error("Invalid auth() arguments"); | ||
} | ||
@@ -333,0 +337,0 @@ } |
@@ -15,1 +15,3 @@ import { BasedClient } from '../index.js'; | ||
export declare const sendAuth: (client: BasedClient, authState: AuthState) => Promise<AuthState>; | ||
export declare const addStreamRegister: (client: BasedClient, reqId: number, contentSize: number, name: string, mimeType: string, extension: string, fnName: string, payload: any) => void; | ||
export declare const addStreamChunk: (client: BasedClient, reqId: number, seqId: number, chunk: Uint8Array, deflate: boolean) => void; |
import { updateAuthState } from '../authState/updateAuthState.js'; | ||
import { encodeAuthMessage, encodeFunctionMessage, encodeGetObserveMessage, encodeObserveMessage, encodePublishMessage, encodeSubscribeChannelMessage, } from './protocol.js'; | ||
import { encodeAuthMessage, encodeFunctionMessage, encodeGetObserveMessage, encodeObserveMessage, encodePublishMessage, encodeStreamMessage, encodeSubscribeChannelMessage, } from './protocol.js'; | ||
import { deepEqual } from '@saulx/utils'; | ||
@@ -21,3 +21,4 @@ const PING = new Uint8Array(0); | ||
client.cQ.size || | ||
client.pQ.length); | ||
client.pQ.length || | ||
client.sQ.length); | ||
}; | ||
@@ -38,2 +39,3 @@ export const drainQueue = (client) => { | ||
const get = client.gQ; | ||
const stream = client.sQ; | ||
const buffs = []; | ||
@@ -71,2 +73,8 @@ let l = 0; | ||
} | ||
// ------- Stream | ||
for (const s of stream) { | ||
const { buffers, len } = encodeStreamMessage(s); | ||
buffs.push(...buffers); | ||
l += len; | ||
} | ||
const n = new Uint8Array(l); | ||
@@ -80,2 +88,3 @@ let c = 0; | ||
client.pQ = []; | ||
client.sQ = []; | ||
client.oQ.clear(); | ||
@@ -201,2 +210,45 @@ client.gQ.clear(); | ||
}; | ||
// ------------ Stream --------------- | ||
export const addStreamRegister = (client, reqId, contentSize, name, mimeType, extension, fnName, payload) => { | ||
client.sQ.push([ | ||
1, | ||
reqId, | ||
contentSize, | ||
name, | ||
mimeType, | ||
extension, | ||
fnName, | ||
payload, | ||
]); | ||
drainQueue(client); | ||
}; | ||
export const addStreamChunk = (client, reqId, seqId, chunk, deflate) => { | ||
// lets send the chunks of streams directly | ||
// also need to keep the amount we push in here to a minimum | ||
// dc for streams will not resend them | ||
// client.sQ.push([2, reqId, seqId, chunk]) | ||
// TODO: Add progress listener (send seqId back or multiple) | ||
if (client.connected) { | ||
// how to get progress | ||
const { len, buffers } = encodeStreamMessage([ | ||
2, | ||
reqId, | ||
seqId, | ||
chunk, | ||
deflate, | ||
]); | ||
const n = new Uint8Array(len); | ||
let c = 0; | ||
for (const b of buffers) { | ||
n.set(b, c); | ||
c += b.length; | ||
} | ||
client.connection.ws.send(n); | ||
idleTimeout(client); | ||
} | ||
else { | ||
// console.info('for streams you need to be connected', this can other wiser overflow) | ||
client.sQ.push([2, reqId, seqId, chunk, deflate]); | ||
} | ||
}; | ||
//# sourceMappingURL=index.js.map |
import { AuthState } from '../types/auth.js'; | ||
import { ChannelPublishQueueItem, ChannelQueueItem, FunctionQueueItem, GetObserveQueue, ObserveQueue } from '../types/index.js'; | ||
import { ChannelPublishQueueItem, ChannelQueueItem, FunctionQueueItem, GetObserveQueue, ObserveQueue, StreamQueueItem } from '../types/index.js'; | ||
export declare const encodeGetObserveMessage: (id: number, o: import("../types/observe.js").GetObserveQueueItem) => { | ||
@@ -24,1 +24,5 @@ buffers: Uint8Array[]; | ||
export declare const encodeAuthMessage: (authState: AuthState) => Uint8Array; | ||
export declare const encodeStreamMessage: (f: StreamQueueItem) => { | ||
buffers: Uint8Array[]; | ||
len: number; | ||
}; |
@@ -20,3 +20,5 @@ import { deflateSync } from 'fflate'; | ||
// 6 = publishChannel | ||
// 7 = unsubscribeChannel | ||
// 7.0 = unsubscribeChannel | ||
// 7.1 = register stream | ||
// 7.2 = chunk | ||
// isDeflate (1 bit) | ||
@@ -81,7 +83,8 @@ // len (28 bits) | ||
// Type 7 = unsubscribe | ||
// | 4 header | 8 id | | ||
// | 4 header | 1 = 0 | 8 id | | ||
if (type === 7) { | ||
const buff = createBuffer(type, false, 12); | ||
storeUint8(buff, id, 4, 8); | ||
return { buffers: [buff], len: 12 }; | ||
const buff = createBuffer(type, false, 13); | ||
storeUint8(buff, 0, 4, 1); | ||
storeUint8(buff, id, 5, 8); | ||
return { buffers: [buff], len: 13 }; | ||
} | ||
@@ -180,2 +183,79 @@ const n = encoder.encode(name); | ||
}; | ||
export const encodeStreamMessage = (f) => { | ||
const [subType, reqId] = f; | ||
// Type 7.1 Start stream | ||
// | 4 header | 1 subType = 1 | 3 reqId | 4 content-size | 1 nameLen | 1 mimeLen | 1 fnNameLen | 1 extensionLength | name | mime | fnName | extension | payload | ||
if (subType === 1) { | ||
const [, , contentSize, name, mimeType, extension, fnName, payload] = f; | ||
let sLen = 16; | ||
let len = sLen; | ||
const nameEncoded = encoder.encode(name); | ||
len += nameEncoded.length; | ||
const [isDeflate, p] = encodePayload(payload); | ||
if (p) { | ||
len += p.length; | ||
} | ||
const mimeTypeEncoded = encoder.encode(mimeType); | ||
len += mimeTypeEncoded.length; | ||
const fnNameEncoded = encoder.encode(fnName); | ||
len += fnNameEncoded.length; | ||
const extensionEncoded = encoder.encode(extension); | ||
len += extensionEncoded.length; | ||
const buff = createBuffer(7, isDeflate, len, sLen); | ||
storeUint8(buff, 1, 4, 1); | ||
storeUint8(buff, reqId, 5, 3); | ||
storeUint8(buff, contentSize, 8, 4); | ||
storeUint8(buff, nameEncoded.length, 12, 1); | ||
storeUint8(buff, mimeTypeEncoded.length, 13, 1); | ||
storeUint8(buff, fnNameEncoded.length, 14, 1); | ||
storeUint8(buff, extensionEncoded.length, 15, 1); | ||
if (p) { | ||
return { | ||
buffers: [ | ||
buff, | ||
nameEncoded, | ||
mimeTypeEncoded, | ||
fnNameEncoded, | ||
extensionEncoded, | ||
p, | ||
], | ||
len, | ||
}; | ||
} | ||
return { | ||
buffers: [ | ||
buff, | ||
nameEncoded, | ||
mimeTypeEncoded, | ||
fnNameEncoded, | ||
extensionEncoded, | ||
], | ||
len, | ||
}; | ||
} | ||
else if (subType === 2) { | ||
// Type 7.2 Chunk | ||
// | 4 header | 1 subType = 2 | 3 reqId | 1 seqId | content | ||
let sLen = 9; | ||
let len = sLen; | ||
const [, , seqId, chunk] = f; | ||
// only deflate is it makes sense | ||
let isDeflate = false; | ||
let processed = chunk; | ||
if (chunk.length > 150) { | ||
processed = deflateSync(chunk); | ||
len += processed.length; | ||
isDeflate = true; | ||
} | ||
else { | ||
len += chunk.length; | ||
} | ||
const buff = createBuffer(7, isDeflate, len, sLen); | ||
storeUint8(buff, 2, 4, 1); | ||
storeUint8(buff, reqId, 5, 3); | ||
storeUint8(buff, seqId, 8, 1); | ||
return { buffers: [buff, processed], len }; | ||
} | ||
return { buffers: [], len: 0 }; | ||
}; | ||
//# sourceMappingURL=protocol.js.map |
import { BasedClient } from '../index.js'; | ||
import { StreamFunctionOpts } from './types.js'; | ||
declare const _default: (client: BasedClient, name: string, options: StreamFunctionOpts, progressListener?: (progress: number) => void) => Promise<any>; | ||
import { StreamFunctionContents } from './types.js'; | ||
export declare const isStreaming: { | ||
streaming: boolean; | ||
}; | ||
declare const _default: (client: BasedClient, name: string, options: StreamFunctionContents, progressListener?: (progress: number) => void) => Promise<any>; | ||
export default _default; |
@@ -1,21 +0,12 @@ | ||
import { isFileContents, isStreamFunctionPath, isStreamFunctionStream, } from './types.js'; | ||
import uploadFileBrowser from './uploadFileBrowser.js'; | ||
import fetch from './fetch.js'; | ||
import { isFileContents } from './types.js'; | ||
import { uploadFile } from './browserStream.js'; | ||
export const isStreaming = { streaming: false }; | ||
// will get browser stream as well | ||
export default async (client, name, options, progressListener) => { | ||
if (isStreamFunctionPath(options)) { | ||
return; | ||
} | ||
if (isStreamFunctionStream(options)) { | ||
return; | ||
} | ||
if (options.contents instanceof ArrayBuffer) { | ||
if (options.contents instanceof ArrayBuffer || | ||
typeof options.contents === 'string') { | ||
options.contents = new global.Blob([options.contents], { | ||
type: options.mimeType || 'text/plain', | ||
}); | ||
// want to stream this XHR browser / stream + http nodejs | ||
return fetch(client, name, options); | ||
} | ||
if (isFileContents(options)) { | ||
return uploadFileBrowser(client, name, options, progressListener); | ||
} | ||
if (options.contents instanceof global.Blob) { | ||
@@ -25,8 +16,9 @@ if (!options.mimeType) { | ||
} | ||
// want to stream this XHR browser / stream + http nodejs | ||
return fetch(client, name, options); | ||
options.contents = new File([options.contents], options.fileName || 'blob', { type: options.contents.type }); | ||
} | ||
if (typeof options.contents === 'string') { | ||
// want to stream this XHR browser / stream + http nodejs | ||
return fetch(client, name, options); | ||
if (isFileContents(options)) { | ||
if (!options.size) { | ||
options.size = options.contents.size; | ||
} | ||
return uploadFile(client, name, options, progressListener); | ||
} | ||
@@ -33,0 +25,0 @@ throw new Error(`Invalid opts for file api ${name} ${JSON.stringify(options, null, 2)}`); |
import { BasedClient } from '../index.js'; | ||
import { StreamFunctionOpts } from './types.js'; | ||
declare const _default: (client: BasedClient, name: string, options: StreamFunctionOpts, _progressListener?: (progress: number) => void) => Promise<any>; | ||
export declare const isStreaming: { | ||
streaming: boolean; | ||
}; | ||
declare const _default: (client: BasedClient, name: string, options: StreamFunctionOpts, progressListener?: (progress: number, bytes: number) => void) => Promise<any>; | ||
export default _default; |
import { isStreamFunctionPath, isStreamFunctionStream, } from './types.js'; | ||
import fetch from './fetch.js'; | ||
import { Readable } from 'node:stream'; | ||
import { uploadFilePath, uploadFileStream } from './nodeStream.js'; | ||
export default async (client, name, options, _progressListener) => { | ||
import { Buffer } from 'node:buffer'; | ||
export const isStreaming = { streaming: false }; | ||
async function* generateChunks(bytes) { | ||
// 100kb (bit arbitrary) | ||
const readBytes = 100000; | ||
let index = 0; | ||
while (index * readBytes < bytes.byteLength) { | ||
const buf = bytes.slice(index * readBytes, Math.min(bytes.byteLength, (index + 1) * readBytes)); | ||
index++; | ||
yield Buffer.from(buf); | ||
} | ||
} | ||
const createReadableStreamFromContents = (bytes) => { | ||
return Readable.from(generateChunks(bytes)); | ||
}; | ||
export default async (client, name, options, progressListener) => { | ||
if (isStreamFunctionPath(options)) { | ||
return uploadFilePath(client, name, options); | ||
return uploadFilePath(client, name, options, progressListener); | ||
} | ||
if (isStreamFunctionStream(options)) { | ||
return uploadFileStream(client, name, options); | ||
return uploadFileStream(client, name, options, progressListener); | ||
} | ||
if (options.contents instanceof ArrayBuffer) { | ||
options.contents = global.Buffer.from(options.contents); | ||
return fetch(client, name, options); | ||
if (options.contents instanceof Buffer) { | ||
options.contents = new Uint8Array(options.contents.buffer, options.contents.byteOffset, options.contents.length); | ||
} | ||
if (typeof options.contents === 'string' || | ||
options.contents instanceof global.Buffer) { | ||
return fetch(client, name, options); | ||
if (typeof options.contents === 'string') { | ||
options.contents = new TextEncoder().encode(options.contents); | ||
} | ||
if (options.contents instanceof Uint8Array) { | ||
return uploadFileStream(client, name, { | ||
...options, | ||
size: options.contents.byteLength, | ||
contents: createReadableStreamFromContents(options.contents), | ||
}, progressListener); | ||
} | ||
throw new Error(`Invalid opts for file api ${name} ${JSON.stringify(options, null, 2)}`); | ||
}; | ||
//# sourceMappingURL=index.js.map |
/// <reference types="node" resolution-mode="require"/> | ||
import { Readable } from 'stream'; | ||
import { StreamFunctionPath, StreamFunctionStream } from './types.js'; | ||
import { BasedClient } from '../index.js'; | ||
import { Readable } from "stream"; | ||
import { StreamFunctionPath, StreamFunctionStream } from "./types.js"; | ||
import { BasedClient } from "../index.js"; | ||
export declare const isStream: (contents: any) => contents is Readable; | ||
export declare const uploadFilePath: (client: BasedClient, name: string, options: StreamFunctionPath) => Promise<any>; | ||
export declare const uploadFileStream: (client: BasedClient, name: string, options: StreamFunctionStream) => Promise<any>; | ||
export declare const uploadFilePath: (client: BasedClient, name: string, options: StreamFunctionPath, progressListener?: (p: number, bytes: number) => void) => Promise<any>; | ||
export declare const uploadFileStream: (client: BasedClient, name: string, options: StreamFunctionStream, progressListener?: (p: number, bytes: number) => void) => Promise<any>; |
@@ -1,10 +0,5 @@ | ||
import { Readable } from 'stream'; | ||
import { request } from 'http'; | ||
import { request as sslRequest } from 'https'; | ||
import fs from 'fs'; | ||
import { promisify } from 'util'; | ||
import { encodeAuthState } from '../index.js'; | ||
import parseOpts from '@based/opts'; | ||
import { serializeQuery } from '@saulx/utils'; | ||
import { convertDataToBasedError } from '@based/errors'; | ||
import { Readable, Writable } from "stream"; | ||
import fs from "fs"; | ||
import { promisify } from "util"; | ||
import { addStreamChunk, addStreamRegister } from "../outgoing/index.js"; | ||
const stat = promisify(fs.stat); | ||
@@ -23,4 +18,3 @@ const checkFile = async (path) => { | ||
}; | ||
const parseUrlRe = /^(?:(tcp|wss?|https?):\/\/)?([a-z0-9.-]*)(?::(\d+))?$/; | ||
export const uploadFilePath = async (client, name, options) => { | ||
export const uploadFilePath = async (client, name, options, progressListener) => { | ||
const info = await checkFile(options.path); | ||
@@ -35,3 +29,3 @@ if (info) { | ||
serverKey: options.serverKey, | ||
}); | ||
}, progressListener); | ||
} | ||
@@ -42,42 +36,3 @@ else { | ||
}; | ||
const streamRequest = (stream, name, url, headers, query) => { | ||
const [, protocol, host, port] = parseUrlRe.exec(url); | ||
// query | ||
const httpOptions = { | ||
port, | ||
host: host, | ||
path: '/' + name + query, | ||
method: 'POST', | ||
headers, | ||
}; | ||
return new Promise((resolve, reject) => { | ||
const incomingReady = (incomingReq) => { | ||
const s = []; | ||
incomingReq.on('data', (c) => { | ||
s.push(c.toString()); | ||
}); | ||
incomingReq.once('end', () => { | ||
const result = s.join(''); | ||
try { | ||
const parsed = JSON.parse(result); | ||
if ('code' in parsed && 'error' in parsed) { | ||
reject(convertDataToBasedError({ | ||
code: parsed.code, | ||
message: parsed.error, | ||
})); | ||
return; | ||
} | ||
resolve(parsed); | ||
} | ||
catch (err) { } | ||
resolve(result); | ||
}); | ||
}; | ||
const req = protocol === 'wss' || protocol === 'https' | ||
? sslRequest(httpOptions, incomingReady) | ||
: request(httpOptions, incomingReady); | ||
stream.pipe(req); | ||
}); | ||
}; | ||
export const uploadFileStream = async (client, name, options) => { | ||
export const uploadFileStream = async (client, name, options, progressListener) => { | ||
if (!(options.contents instanceof Readable)) { | ||
@@ -87,23 +42,109 @@ throw new Error('File Contents has to be an instance of "Readable"'); | ||
if (!client.connected) { | ||
await client.once('connect'); | ||
await client.once("connect"); | ||
} | ||
// key is something special | ||
const url = await parseOpts(client.opts, true); | ||
const headers = { | ||
'Content-Length': String(options.size), | ||
'Content-Type': options.mimeType || 'text/plain', | ||
Authorization: encodeAuthState(client.authState), | ||
}; | ||
if (options.fileName) { | ||
headers['Content-Name'] = options.fileName; | ||
let reqId = ++client.streamRequestId; | ||
if (reqId > 16777215) { | ||
reqId = client.streamRequestId = 0; | ||
} | ||
if (!options.mimeType && options.extension) { | ||
headers['Content-Extension'] = options.extension; | ||
let seqId = 0; | ||
addStreamRegister(client, reqId, options.size, options.fileName, options.mimeType, options.extension, name, options.payload); | ||
const useDeflate = !(options.mimeType | ||
? /image|video|x-zip/i.test(options.mimeType) | ||
: options.extension | ||
? /(mp4|avi|mov|zip|jpg|jpeg|png|gif|mkv)/i.test(options.extension) | ||
: false); | ||
// 100kb | ||
const smallest = 100000; | ||
// 10mb | ||
const maxSize = 1000000 * 10; | ||
// 1mb | ||
const medium = 1000000 * 1; | ||
let readSize = Math.min(medium, options.size); | ||
if (options.size < medium * 5) { | ||
readSize = Math.min(smallest, options.size); | ||
} | ||
let q = ''; | ||
if (options.payload) { | ||
q = '?' + serializeQuery(options.payload); | ||
else if (options.size > medium * 100) { | ||
readSize = maxSize; | ||
} | ||
return streamRequest(options.contents, name, url, headers, q); | ||
let bufferSize = 0; | ||
let nextHandler; | ||
let chunks = []; | ||
let lastReceived = 0; | ||
let totalBytes = 0; | ||
let streamHandler; | ||
const wr = new Writable({ | ||
write: function (c, encoding, next) { | ||
if (c.byteLength > maxSize) { | ||
console.warn("CHUNK SIZE LARGER THEN MAX SIZE NOT HANDLED YET", c.byteLength, maxSize); | ||
} | ||
bufferSize += c.byteLength; | ||
chunks.push(c); | ||
if (bufferSize >= readSize || totalBytes + bufferSize === options.size) { | ||
nextHandler = next; | ||
if (seqId > 0) { | ||
if (lastReceived === seqId) { | ||
// Client is slower then server (most common) | ||
nextChunk(undefined); | ||
} | ||
// Else server is slower then client e.g. transcoding etc | ||
} | ||
else { | ||
setTimeout(nextChunk, 0); | ||
} | ||
} | ||
else { | ||
next(); | ||
} | ||
}, | ||
}); | ||
const nextChunk = (receivedSeqId, code, maxChunkSize) => { | ||
if (receivedSeqId !== undefined) { | ||
if (maxChunkSize) { | ||
// set readSize if sefver is busy | ||
readSize = maxChunkSize; | ||
} | ||
lastReceived = receivedSeqId; | ||
} | ||
if (!nextHandler) { | ||
return; | ||
} | ||
if (code === 1) { | ||
progressListener(1, options.size); | ||
} | ||
else { | ||
const n = new Uint8Array(bufferSize); | ||
let c = 0; | ||
for (const b of chunks) { | ||
n.set(b, c); | ||
c += b.length; | ||
} | ||
if (progressListener) { | ||
progressListener(totalBytes / options.size, totalBytes); | ||
} | ||
totalBytes += bufferSize; | ||
if (seqId === 255) { | ||
seqId = 0; | ||
} | ||
addStreamChunk(client, reqId, ++seqId, n, useDeflate); | ||
bufferSize = 0; | ||
chunks = []; | ||
nextHandler(); | ||
nextHandler = undefined; | ||
} | ||
}; | ||
options.contents.pipe(wr); | ||
let id = Math.random().toString(16); | ||
const dcHandler = () => { | ||
// HANDLE THIS | ||
console.error("CLIENT DC -> ABORT STREAM", Date.now(), id); | ||
}; | ||
client.once("disconnect", dcHandler); | ||
options.contents.on("end", () => { | ||
client.off("disconnect", dcHandler); | ||
}); | ||
return new Promise((resolve, reject) => { | ||
streamHandler = [resolve, reject, nextChunk]; | ||
client.streamFunctionResponseListeners.set(reqId, streamHandler); | ||
}); | ||
}; | ||
//# sourceMappingURL=nodeStream.js.map |
@@ -6,3 +6,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
export type ProgressListener = (progress: number, files: number) => void; | ||
export type StreamFunctionContents<F = Buffer | ArrayBuffer | string | File | Blob> = { | ||
export type StreamFunctionContents<F = Buffer | Uint8Array | string | File | Blob> = { | ||
contents: F; | ||
@@ -13,2 +13,4 @@ payload?: any; | ||
serverKey?: string; | ||
extension?: string; | ||
size?: number; | ||
}; | ||
@@ -33,10 +35,26 @@ export declare const isFileContents: (contents: StreamFunctionContents) => contents is StreamFunctionContents<File>; | ||
export type StreamFunctionOpts = StreamFunctionPath | StreamFunctionContents | StreamFunctionStream; | ||
export type StreamHeaders = { | ||
'Content-Extension'?: string; | ||
'Content-Length'?: string; | ||
'Content-Type': string; | ||
'Content-Name'?: string; | ||
Authorization: string; | ||
}; | ||
export declare const isStreamFunctionPath: (options: StreamFunctionOpts) => options is StreamFunctionPath; | ||
export declare const isStreamFunctionStream: (options: StreamFunctionOpts) => options is StreamFunctionStream; | ||
export type StreamQueueItem = [ | ||
1, | ||
number, | ||
number, | ||
string, | ||
string, | ||
string, | ||
string, | ||
any | ||
] | [ | ||
2, | ||
number, | ||
number, | ||
Uint8Array, | ||
boolean | ||
]; | ||
export type StreamQueue = StreamQueueItem[]; | ||
export type StreamResponseHandler = [ | ||
(val?: any) => void, | ||
(err: Error) => void, | ||
(seqId: number, code: number, maxChunkSize: number) => void | ||
]; | ||
export type StreamFunctionResponseListeners = Map<number, StreamResponseHandler>; |
@@ -1,7 +0,8 @@ | ||
export * from './generic.js'; | ||
export * from './observe.js'; | ||
export * from './events.js'; | ||
export * from './auth.js'; | ||
export * from './cache.js'; | ||
export * from './functions.js'; | ||
export * from './channel.js'; | ||
export * from "./generic.js"; | ||
export * from "./observe.js"; | ||
export * from "./events.js"; | ||
export * from "./auth.js"; | ||
export * from "./cache.js"; | ||
export * from "./functions.js"; | ||
export * from "./channel.js"; | ||
export * from "../stream/types.js"; |
@@ -1,8 +0,9 @@ | ||
export * from './generic.js'; | ||
export * from './observe.js'; | ||
export * from './events.js'; | ||
export * from './auth.js'; | ||
export * from './cache.js'; | ||
export * from './functions.js'; | ||
export * from './channel.js'; | ||
export * from "./generic.js"; | ||
export * from "./observe.js"; | ||
export * from "./events.js"; | ||
export * from "./auth.js"; | ||
export * from "./cache.js"; | ||
export * from "./functions.js"; | ||
export * from "./channel.js"; | ||
export * from "../stream/types.js"; | ||
//# sourceMappingURL=index.js.map |
import urlLoader from './urlLoader.js'; | ||
import { encodeAuthState } from '../authState/parseAuthState.js'; | ||
import { isStreaming } from '../stream/uploadFileBrowser.js'; | ||
import { isStreaming } from '../stream/index.js'; | ||
import WebSocket from 'isomorphic-ws'; | ||
@@ -5,0 +5,0 @@ const activityListeners = new Map(); |
{ | ||
"name": "@based/client", | ||
"version": "5.3.0", | ||
"version": "6.0.0", | ||
"license": "MIT", | ||
@@ -13,3 +13,3 @@ "scripts": { | ||
"browserBuildConsole": "esbuild ./test/browser/index.ts --bundle --outfile=./test/browser/out.js --minify --metafile=./test/browser/meta.json --define:global=window", | ||
"browserBuild": "esbuild ./test/browser/index.ts --bundle --outfile=./test/browser/out.js --minify --metafile=./test/browser/meta.json --define:global=window --drop:console", | ||
"browserBuild": "esbuild ./test/browser/index.ts --bundle --outfile=./test/browser/out.js --minify --metafile=./test/browser/meta.json --define:global=window", | ||
"browser": "node ./test/browser/server.js" | ||
@@ -28,3 +28,3 @@ }, | ||
"ava": { | ||
"timeout": "2m", | ||
"timeout": "4m", | ||
"workerThreads": false, | ||
@@ -43,4 +43,5 @@ "files": [ | ||
"@saulx/hash": "^3.0.0", | ||
"@saulx/utils": "^4.1.0", | ||
"@saulx/utils": "^4.3.2", | ||
"@based/fetch": "^2.0.3", | ||
"@based/errors": "^1.2.0", | ||
"@based/opts": "^1.0.0", | ||
@@ -58,2 +59,3 @@ "fflate": "0.8.1", | ||
"@based/functions": "^3.0.1", | ||
"cross-fetch": "4.0.0", | ||
"ava": "5.3.1", | ||
@@ -60,0 +62,0 @@ "typescript": "^5.2.2", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
218747
121
3467
1
9
10
+ Added@based/errors@^1.2.0
+ Added@based/errors@1.4.0(transitive)
+ Added@based/functions@3.0.2(transitive)
+ Addedutility-types@3.11.0(transitive)
Updated@saulx/utils@^4.3.2