diff --git a/core/core-client.ts b/core/core-client.ts index a8f2755..6fe7d4a 100644 --- a/core/core-client.ts +++ b/core/core-client.ts @@ -6,6 +6,7 @@ import b64 from 'base64-js' //types import type { id, PropKey, PropValue, CoreMessageKind, ClientMessageKind, Bit } from './types.js' import type { RateLimit } from './ratelimit.js' +import type { SocioFiles } from './types.js'; type MessageDataObj = { id: id, verb?: string, table?: string, status?:string, result?:string|object|boolean|PropValue, prop?:PropKey, data?:object }; type SubscribeCallbackObjectSuccess = ((res: object | object[]) => void) | null; type SubscribeCallbackObject = { success: SubscribeCallbackObjectSuccess, error?: Function}; @@ -145,12 +146,11 @@ export class SocioClient extends LogHandler { }else throw new E('Not enough prop info sent from server to perform prop update.', data) break; case 'CMD': if(this.lifecycle_hooks?.cmd) this.lifecycle_hooks.cmd(data?.data); break; //the server pushed some data to this client, let the dev handle it - case 'ERR'://when using this, make sure that the setup query is a promise func. The result field is used as a cause of error msg on the backend - this.#FindID(kind, data?.id); + case 'ERR'://The result field is sometimes used as a cause of error msg on the backend if (typeof this.#queries[data.id] == 'function') - (this.#queries[data.id] as Function)(null); - - throw new E(`Request to Server returned ERROR response for query id, reason #[err-msg-kind]`, data.id, data?.result as Bit); + (this.#queries[data.id] as Function)(); + + this.HandleError(new E(`Request to Server returned ERROR response for query id, reason #[err-msg-kind]`, data?.id, data?.result as Bit)) case 'RECON': this.#FindID(kind, data?.id); //@ts-expect-error @@ -169,7 +169,27 @@ export class SocioClient extends LogHandler { this.#ws?.send(JSON.stringify(Object.assign({}, { kind, data:data[0] }, ...data.slice(1)))); this.HandleInfo('sent:', kind, data); } - SendBlob(blob: Blob | ArrayBuffer | ArrayBufferView){ + async SendFiles(files:File[], other_data:object|undefined={}){ + const proc_files: SocioFiles = {}; //my own kind of FormData, specific for files, because FormData is actually a very riggid type + + //add each file + for(const file of files){ + //relevant info about files is stored in file_meta + const file_meta = { + lastModified: file.lastModified, + size: file.size, + type: file.type + }; + proc_files[file.name] = { file_meta, bin: b64.fromByteArray(new Uint8Array(await file.arrayBuffer()))}; //this is the best way that i could find. JS is really unhappy about binary data + } + + //create the server request as usual + const {id, prom} = this.CreateQueryPromise(); + this.Send('FILES', { id, files:proc_files, other_data}); + + return prom; + } + SendBinary(blob: Blob | ArrayBuffer | ArrayBufferView) { //send binary. Unfortunately, it is not useful for me to invent my own byte formats and build functionality. You can tho. This is just low level access. if (this.#queries[`BLOB`]) throw new E('BLOB already being uploaded. Wait until the last query completes!'); this.#ws?.send(blob); @@ -179,25 +199,6 @@ export class SocioClient extends LogHandler { this.#queries[`BLOB`] = res }); } - async SendFiles(files:File[]){ - if (this.#queries[`FILES`]) throw new E('FILES already being uploaded. Wait until the last query completes!'); - - const proc_files = {}; - - for(const file of files){ - const file_meta = { - lastModified: file.lastModified, - size: file.size, - type: file.type - }; - proc_files[file.name] = { file_meta, bin: b64.fromByteArray(new Uint8Array(await file.arrayBuffer()))}; - } - - const {id, prom} = this.CreateQueryPromise(); - this.Send('FILES', { id:id, files:proc_files}); - - return prom; - } CreateQueryPromise(){ const id = this.GenKey; const prom = new Promise((res) => { @@ -361,10 +362,10 @@ export class SocioClient extends LogHandler { return SocioClient.#key } } - //checks if the ID of a query exists (i.e. has been registered), otherwise rejects and logs + //checks if the ID of a query exists, otherwise rejects and logs #FindID(kind: string, id: id) { if (!(id in this.#queries)) - throw new E(`${kind} message for unregistered SQL query! msg_id -`, id, Object.keys(this.#queries)) + throw new E(`${kind} message for unregistered SQL query! msg_id -`, id); } #HandleBasicPromiseMessage(kind:string, data:MessageDataObj){ this.#FindID(kind, data?.id); diff --git a/core/fs-utils.ts b/core/fs-utils.ts index f1d1385..0418b1e 100644 --- a/core/fs-utils.ts +++ b/core/fs-utils.ts @@ -1,9 +1,9 @@ import fs from 'fs'; import b64 from 'base64-js' import { default as os_path } from "path"; -// import { log } from './logging.js'; -export type SocioFiles = {[filename:string]:{file_meta:{}, bin:string}}; +//types +import type { SocioFiles } from './types.js'; export async function SaveFilesToDiskPath(path_array: string[], files: SocioFiles){ for (const [filename, file_data] of Object.entries(files)) { diff --git a/core/types.d.ts b/core/types.d.ts index 22b6d0f..1e46601 100644 --- a/core/types.d.ts +++ b/core/types.d.ts @@ -1,12 +1,16 @@ +//general types export type id = string | number; export type Bit = 0 | 1; +//props export type PropKey = string; export type PropValue = object | string | number | null; export type PropAssigner = (key: PropKey, new_val:PropValue) => boolean; +//misc +export type SocioFiles = {[filename: string]: {file_meta: {lastModified: number, size: number, type: string}, bin: string } }; //bin is a base64 string of the bytes of the raw file +export type QueryMarker = 'socio' | 'auth' | 'perm'; + //msg kinds export type CoreMessageKind = 'SUB' | 'UNSUB' | 'SQL' | 'PING' | 'AUTH' | 'GET_PERM' | 'PROP_SUB' | 'PROP_UNSUB' | 'PROP_GET' | 'PROP_SET' | 'SERV' | 'ADMIN' | 'RECON' | 'FILES'; -export type ClientMessageKind = 'CON' | 'UPD' | 'PONG' | 'AUTH' | 'GET_PERM' | 'RES' | 'ERR' | 'PROP_UPD' | 'CMD' | 'RECON'; - -export type QueryMarker = 'socio' | 'auth' | 'perm'; \ No newline at end of file +export type ClientMessageKind = 'CON' | 'UPD' | 'PONG' | 'AUTH' | 'GET_PERM' | 'RES' | 'ERR' | 'PROP_UPD' | 'CMD' | 'RECON'; \ No newline at end of file