From 87e442eda9001b811f0ae17978c86903fff19f84 Mon Sep 17 00:00:00 2001 From: hashcoko Date: Thu, 20 Jun 2024 12:59:46 +0900 Subject: [PATCH] Add globalApi JsDoc --- src/lang/index.ts | 2 +- src/ts/storage/globalApi.ts | 979 ++++++++++++++++++++++++++---------- 2 files changed, 708 insertions(+), 273 deletions(-) diff --git a/src/lang/index.ts b/src/lang/index.ts index 5b028eed..c79310f9 100644 --- a/src/lang/index.ts +++ b/src/lang/index.ts @@ -36,4 +36,4 @@ export function changeLanguage(lang:string){ language.help.toggleNsfw = '' language.jailbreakToggle = 'Toggle Togglable Prompt' } -} +} \ No newline at end of file diff --git a/src/ts/storage/globalApi.ts b/src/ts/storage/globalApi.ts index 89a4c23a..5db4961c 100644 --- a/src/ts/storage/globalApi.ts +++ b/src/ts/storage/globalApi.ts @@ -55,6 +55,12 @@ interface fetchLog{ let fetchLog:fetchLog[] = [] +/** + * Downloads a file with the given name and data. + * + * @param {string} name - The name of the file to be downloaded. + * @param {Uint8Array|ArrayBuffer|string} dat - The data of the file to be downloaded. + */ export async function downloadFile(name:string, dat:Uint8Array|ArrayBuffer|string) { if(typeof(dat) === 'string'){ dat = Buffer.from(dat, 'utf-8') @@ -88,6 +94,12 @@ let fileCache:{ let pathCache:{[key:string]:string} = {} let checkedPaths:string[] = [] +/** + * Checks if a file exists in the Capacitor filesystem. + * + * @param {CapFS.GetUriOptions} getUriOptions - The options for getting the URI of the file. + * @returns {Promise} - A promise that resolves to true if the file exists, false otherwise. + */ async function checkCapFileExists(getUriOptions: CapFS.GetUriOptions): Promise { try { await CapFS.Filesystem.stat(getUriOptions); @@ -100,6 +112,13 @@ async function checkCapFileExists(getUriOptions: CapFS.GetUriOptions): Promise} - A promise that resolves to the source URL of the file. + */ export async function getFileSrc(loc:string) { if(isTauri){ if(loc.startsWith('assets')){ @@ -201,6 +220,12 @@ export async function getFileSrc(loc:string) { let appDataDirPath = '' +/** + * Reads an image file and returns its data. + * + * @param {string} data - The path to the image file. + * @returns {Promise} - A promise that resolves to the data of the image file. + */ export async function readImage(data:string) { if(isTauri){ if(data.startsWith('assets')){ @@ -216,6 +241,14 @@ export async function readImage(data:string) { } } +/** + * Saves an asset file with the given data, custom ID, and file name. + * + * @param {Uint8Array} data - The data of the asset file. + * @param {string} [customId=''] - The custom ID for the asset file. + * @param {string} [fileName=''] - The name of the asset file. + * @returns {Promise} - A promise that resolves to the path of the saved asset file. + */ export async function saveAsset(data:Uint8Array, customId:string = '', fileName:string = ''){ let id = '' if(customId !== ''){ @@ -246,6 +279,12 @@ export async function saveAsset(data:Uint8Array, customId:string = '', fileName: } } +/** + * Loads an asset file with the given ID. + * + * @param {string} id - The ID of the asset file to load. + * @returns {Promise} - A promise that resolves to the data of the loaded asset file. + */ export async function loadAsset(id:string){ if(isTauri){ return await readBinaryFile(id,{dir: BaseDirectory.AppData}) @@ -257,8 +296,13 @@ export async function loadAsset(id:string){ let lastSave = '' +/** + * Saves the current state of the database. + * + * @returns {Promise} - A promise that resolves when the database has been saved. + */ export async function saveDb(){ - lastSave =JSON.stringify(get(DataBase)) + lastSave = JSON.stringify(get(DataBase)) let changed = false syncDrive() DataBase.subscribe(() => { @@ -334,7 +378,11 @@ export async function saveDb(){ } } - +/** + * Retrieves the database backups. + * + * @returns {Promise} - A promise that resolves to an array of backup timestamps. + */ async function getDbBackups() { let db = get(DataBase) if(db?.account?.useSync){ @@ -377,6 +425,11 @@ async function getDbBackups() { let usingSw = false +/** + * Loads the application data. + * + * @returns {Promise} - A promise that resolves when the data has been loaded. + */ export async function loadData() { const loaded = get(loadedStore) if(!loaded){ @@ -523,31 +576,53 @@ export async function loadData() { } } -export async function getFetchData(id:string) { - for(const log of fetchLog){ - if(log.chatId === id){ - return log - } +/** + * Retrieves fetch data for a given chat ID. + * + * @param {string} id - The chat ID to search for in the fetch log. + * @returns {fetchLog | null} - The fetch log entry if found, otherwise null. + */ +export async function getFetchData(id: string) { + for (const log of fetchLog) { + if (log.chatId === id) { + return log; + } } - return null + return null; } -function updateErrorHandling(){ - removeDefaultHandler() - const errorHandler = (event: ErrorEvent) => { - console.error(event.error) - alertError(event.error) - } - const rejectHandler = (event: PromiseRejectionEvent) => { - console.error(event.reason) - alertError(event.reason) - } - window.addEventListener('error', errorHandler) - window.addEventListener('unhandledrejection', rejectHandler) +/** + * Updates the error handling by removing the default handler and adding custom handlers for errors and unhandled promise rejections. + */ +function updateErrorHandling() { + removeDefaultHandler(); + const errorHandler = (event: ErrorEvent) => { + console.error(event.error); + alertError(event.error); + }; + const rejectHandler = (event: PromiseRejectionEvent) => { + console.error(event.reason); + alertError(event.reason); + }; + window.addEventListener('error', errorHandler); + window.addEventListener('unhandledrejection', rejectHandler); } -const knownHostes = ["localhost","127.0.0.1","0.0.0.0"] +const knownHostes = ["localhost", "127.0.0.1", "0.0.0.0"]; +/** + * Interface representing the arguments for the global fetch function. + * + * @interface GlobalFetchArgs + * @property {boolean} [plainFetchForce] - Whether to force plain fetch. + * @property {any} [body] - The body of the request. + * @property {{ [key: string]: string }} [headers] - The headers of the request. + * @property {boolean} [rawResponse] - Whether to return the raw response. + * @property {'POST' | 'GET'} [method] - The HTTP method to use. + * @property {AbortSignal} [abortSignal] - The abort signal to cancel the request. + * @property {boolean} [useRisuToken] - Whether to use the Risu token. + * @property {string} [chatId] - The chat ID associated with the request. + */ interface GlobalFetchArgs { plainFetchForce?: boolean; body?: any; @@ -559,70 +634,103 @@ interface GlobalFetchArgs { chatId?: string; } +/** + * Interface representing the result of the global fetch function. + * + * @interface GlobalFetchResult + * @property {boolean} ok - Whether the request was successful. + * @property {any} data - The data returned from the request. + * @property {{ [key: string]: string }} headers - The headers returned from the request. + */ interface GlobalFetchResult { ok: boolean; data: any; headers: { [key: string]: string }; } -export function addFetchLog(arg:{ - body:any, - headers?:{[key:string]:string}, - response:any, - success:boolean, - url:string, - resType?:string, - chatId?:string -}){ +/** + * Adds a fetch log entry. + * + * @param {Object} arg - The arguments for the fetch log entry. + * @param {any} arg.body - The body of the request. + * @param {{ [key: string]: string }} [arg.headers] - The headers of the request. + * @param {any} arg.response - The response from the request. + * @param {boolean} arg.success - Whether the request was successful. + * @param {string} arg.url - The URL of the request. + * @param {string} [arg.resType] - The response type. + * @param {string} [arg.chatId] - The chat ID associated with the request. + * @returns {number} - The index of the added fetch log entry. + */ +export function addFetchLog(arg: { + body: any, + headers?: { [key: string]: string }, + response: any, + success: boolean, + url: string, + resType?: string, + chatId?: string +}): number { fetchLog.unshift({ - body: typeof(arg.body) === 'string' ? arg.body : JSON.stringify(arg.body, null, 2), - header: JSON.stringify(arg.headers ?? {}, null, 2), - response: typeof(arg.response) === 'string' ? arg.response : JSON.stringify(arg.response, null, 2), - responseType: arg.resType ?? 'json', - success: arg.success, - date: (new Date()).toLocaleTimeString(), - url: arg.url, - chatId: arg.chatId - }) - return fetchLog.length - 1 + body: typeof (arg.body) === 'string' ? arg.body : JSON.stringify(arg.body, null, 2), + header: JSON.stringify(arg.headers ?? {}, null, 2), + response: typeof (arg.response) === 'string' ? arg.response : JSON.stringify(arg.response, null, 2), + responseType: arg.resType ?? 'json', + success: arg.success, + date: (new Date()).toLocaleTimeString(), + url: arg.url, + chatId: arg.chatId + }); + return fetchLog.length - 1; } - - +/** + * Performs a global fetch request. + * + * @param {string} url - The URL to fetch. + * @param {GlobalFetchArgs} [arg={}] - The arguments for the fetch request. + * @returns {Promise} - The result of the fetch request. + */ export async function globalFetch(url: string, arg: GlobalFetchArgs = {}): Promise { try { - const db = get(DataBase) - const method = arg.method ?? "POST" - db.requestmet = "normal" + const db = get(DataBase); + const method = arg.method ?? "POST"; + db.requestmet = "normal"; - if (arg.abortSignal?.aborted) { return { ok: false, data: 'aborted', headers: {} }} + if (arg.abortSignal?.aborted) { return { ok: false, data: 'aborted', headers: {} }; } - const urlHost = new URL(url).hostname - const forcePlainFetch = (knownHostes.includes(urlHost) && !isTauri) || db.usePlainFetch || arg.plainFetchForce + const urlHost = new URL(url).hostname; + const forcePlainFetch = (knownHostes.includes(urlHost) && !isTauri) || db.usePlainFetch || arg.plainFetchForce; - if (knownHostes.includes(urlHost) && !isTauri && !isNodeServer){ - return { ok: false, headers: {}, data: 'You are trying local request on web version. This is not allowed due to browser security policy. Use the desktop version instead, or use a tunneling service like ngrok and set the CORS to allow all.' } + if (knownHostes.includes(urlHost) && !isTauri && !isNodeServer) { + return { ok: false, headers: {}, data: 'You are trying local request on web version. This is not allowed due to browser security policy. Use the desktop version instead, or use a tunneling service like ngrok and set the CORS to allow all.' }; } // Simplify the globalFetch function: Detach built-in functions if (forcePlainFetch) { - return await fetchWithPlainFetch(url, arg); + return await fetchWithPlainFetch(url, arg); } if (isTauri) { - return await fetchWithTauri(url, arg); + return await fetchWithTauri(url, arg); } if (Capacitor.isNativePlatform()) { - return await fetchWithCapacitor(url, arg); + return await fetchWithCapacitor(url, arg); } return await fetchWithProxy(url, arg); - + } catch (error) { console.error(error); return { ok: false, data: `${error}`, headers: {} }; } } -// Decoupled globalFetch built-in function +/** + * Adds a fetch log entry in the global fetch log. + * + * @param {any} response - The response data. + * @param {boolean} success - Indicates if the fetch was successful. + * @param {string} url - The URL of the fetch request. + * @param {GlobalFetchArgs} arg - The arguments for the fetch request. + */ function addFetchLogInGlobalFetch(response:any, success:boolean, url:string, arg:GlobalFetchArgs){ try{ fetchLog.unshift({ @@ -652,7 +760,13 @@ function addFetchLogInGlobalFetch(response:any, success:boolean, url:string, arg } } -// Decoupled globalFetch built-in function +/** + * Performs a fetch request using plain fetch. + * + * @param {string} url - The URL to fetch. + * @param {GlobalFetchArgs} arg - The arguments for the fetch request. + * @returns {Promise} - The result of the fetch request. + */ async function fetchWithPlainFetch(url: string, arg: GlobalFetchArgs): Promise { try { const headers = { 'Content-Type': 'application/json', ...arg.headers }; @@ -666,7 +780,13 @@ async function fetchWithPlainFetch(url: string, arg: GlobalFetchArgs): Promise} - The result of the fetch request. + */ async function fetchWithTauri(url: string, arg: GlobalFetchArgs): Promise { const body = !arg.body ? null : arg.body instanceof URLSearchParams ? Body.text(arg.body.toString()) : Body.json(arg.body); const headers = arg.headers ?? {}; @@ -696,7 +816,13 @@ async function fetchWithTauri(url: string, arg: GlobalFetchArgs): Promise} - The result of the fetch request. + */ async function fetchWithCapacitor(url: string, arg: GlobalFetchArgs): Promise { const { body, headers = {}, rawResponse } = arg; headers["Content-Type"] = body instanceof URLSearchParams ? "application/x-www-form-urlencoded" : "application/json"; @@ -712,7 +838,13 @@ async function fetchWithCapacitor(url: string, arg: GlobalFetchArgs): Promise} - The result of the fetch request. + */ async function fetchWithProxy(url: string, arg: GlobalFetchArgs): Promise { try { const furl = !isTauri && !isNodeServer ? `${hubURL}/proxy2` : `/proxy2`; @@ -750,263 +882,314 @@ async function fetchWithProxy(url: string, arg: GlobalFetchArgs): Promise} - A promise that resolves when the service worker is registered and initialized. + */ async function registerSw() { await navigator.serviceWorker.register("/sw.js", { scope: "/" }); - await sleep(100) - const da = await fetch('/sw/init') - if(!(da.status >= 200 && da.status < 300)){ - location.reload() - } - else{ - + await sleep(100); + const da = await fetch('/sw/init'); + if (!(da.status >= 200 && da.status < 300)) { + location.reload(); } } -const re = /\\/g -function getBasename(data:string){ - const splited = data.replace(re, '/').split('/') - const lasts = splited[splited.length-1] - return lasts +/** + * Regular expression to match backslashes. + * + * @constant {RegExp} + */ +const re = /\\/g; + +/** + * Gets the basename of a given path. + * + * @param {string} data - The path to get the basename from. + * @returns {string} - The basename of the path. + */ +function getBasename(data: string) { + const splited = data.replace(re, '/').split('/'); + const lasts = splited[splited.length - 1]; + return lasts; } -export function getUnpargeables(db:Database, uptype:'basename'|'pure' = 'basename') { - let unpargeable:string[] = [] +/** + * Retrieves unpargeable resources from the database. + * + * @param {Database} db - The database to retrieve unpargeable resources from. + * @param {'basename'|'pure'} [uptype='basename'] - The type of unpargeable resources to retrieve. + * @returns {string[]} - An array of unpargeable resources. + */ +export function getUnpargeables(db: Database, uptype: 'basename' | 'pure' = 'basename') { + let unpargeable: string[] = []; - function addUnparge(data:string){ - if(!data){ - return + /** + * Adds a resource to the unpargeable list if it is not already included. + * + * @param {string} data - The resource to add. + */ + function addUnparge(data: string) { + if (!data) { + return; } - if(data === ''){ - return + if (data === '') { + return; } - const bn = uptype === 'basename' ? getBasename(data) : data - if(!unpargeable.includes(bn)){ - unpargeable.push(bn) + const bn = uptype === 'basename' ? getBasename(data) : data; + if (!unpargeable.includes(bn)) { + unpargeable.push(bn); } } - addUnparge(db.customBackground) - addUnparge(db.userIcon) + addUnparge(db.customBackground); + addUnparge(db.userIcon); - for(const cha of db.characters){ - if(cha.image){ - addUnparge(cha.image) + for (const cha of db.characters) { + if (cha.image) { + addUnparge(cha.image); } - if(cha.emotionImages){ - for(const em of cha.emotionImages){ - addUnparge(em[1]) + if (cha.emotionImages) { + for (const em of cha.emotionImages) { + addUnparge(em[1]); } } - if(cha.type !== 'group'){ - if(cha.additionalAssets){ - for(const em of cha.additionalAssets){ - addUnparge(em[1]) + if (cha.type !== 'group') { + if (cha.additionalAssets) { + for (const em of cha.additionalAssets) { + addUnparge(em[1]); } } - if(cha.vits){ - const keys = Object.keys(cha.vits.files) - for(const key of keys){ - const vit = cha.vits.files[key] - addUnparge(vit) + if (cha.vits) { + const keys = Object.keys(cha.vits.files); + for (const key of keys) { + const vit = cha.vits.files[key]; + addUnparge(vit); } } - if(cha.ccAssets){ - for(const asset of cha.ccAssets){ - addUnparge(asset.uri) + if (cha.ccAssets) { + for (const asset of cha.ccAssets) { + addUnparge(asset.uri); } } } } - if(db.personas){ + if (db.personas) { db.personas.map((v) => { - addUnparge(v.icon) - }) + addUnparge(v.icon); + }); } - return unpargeable + return unpargeable; } -export function replaceDbResources(db:Database,replacer:{[key:string]:string}) { - let unpargeable:string[] = [] +/** + * Replaces database resources with the provided replacer object. + * + * @param {Database} db - The database object containing resources to be replaced. + * @param {{[key: string]: string}} replacer - An object mapping original resource keys to their replacements. + * @returns {Database} - The updated database object with replaced resources. + */ +export function replaceDbResources(db: Database, replacer: { [key: string]: string }): Database { + let unpargeable: string[] = []; - function replaceData(data:string){ - if(!data){ - return data + /** + * Replaces a given data string with its corresponding value from the replacer object. + * + * @param {string} data - The data string to be replaced. + * @returns {string} - The replaced data string or the original data if no replacement is found. + */ + function replaceData(data: string): string { + if (!data) { + return data; } - return replacer[data] ?? data + return replacer[data] ?? data; } - db.customBackground = replaceData(db.customBackground) - db.userIcon = replaceData(db.userIcon) + db.customBackground = replaceData(db.customBackground); + db.userIcon = replaceData(db.userIcon); - for(const cha of db.characters){ - if(cha.image){ - cha.image = replaceData(cha.image) + for (const cha of db.characters) { + if (cha.image) { + cha.image = replaceData(cha.image); } - if(cha.emotionImages){ - for(let i=0;i} - A promise that resolves when the database format check and update is complete. + */ +async function checkNewFormat(): Promise { + let db = get(DataBase); - //check data integrity + // Check data integrity db.characters = db.characters.map((v) => { - if(!v){ - return null + if (!v) { + return null; } - v.chaId ??= uuidv4() - v.type ??= 'character' - v.chatPage ??= 0 - v.chats ??= [] - v.customscript ??= [] - v.firstMessage ??= '' - v.globalLore ??= [] - v.name ??= '' - v.viewScreen ??= 'none' - v.emotionImages = v.emotionImages ?? [] + v.chaId ??= uuidv4(); + v.type ??= 'character'; + v.chatPage ??= 0; + v.chats ??= []; + v.customscript ??= []; + v.firstMessage ??= ''; + v.globalLore ??= []; + v.name ??= ''; + v.viewScreen ??= 'none'; + v.emotionImages = v.emotionImages ?? []; - if(v.type === 'character'){ - v.bias ??= [] - v.characterVersion ??= '' - v.creator ??= '' - v.desc ??= '' - v.utilityBot ??= false - v.tags ??= [] - v.systemPrompt ??= '' - v.scenario ??= '' + if (v.type === 'character') { + v.bias ??= []; + v.characterVersion ??= ''; + v.creator ??= ''; + v.desc ??= ''; + v.utilityBot ??= false; + v.tags ??= []; + v.systemPrompt ??= ''; + v.scenario ??= ''; } - return v + return v; }).filter((v) => { - return v !== null - }) + return v !== null; + }); db.modules = (db.modules ?? []).map((v) => { - if(v.lorebook){ - v.lorebook = updateLorebooks(v.lorebook) + if (v.lorebook) { + v.lorebook = updateLorebooks(v.lorebook); } - return v - }) + return v; + }); - if(!db.formatversion){ - function checkParge(data:string){ - - if(data.startsWith('assets') || (data.length < 3)){ - return data - } - else{ - const d = 'assets/' + (data.replace(/\\/g, '/').split('assets/')[1]) - if(!d){ - return data + if (!db.formatversion) { + /** + * Checks and updates the path of a given data string. + * + * @param {string} data - The data string to be checked and updated. + * @returns {string} - The updated data string with the correct path. + */ + function checkParge(data: string): string { + if (data.startsWith('assets') || (data.length < 3)) { + return data; + } else { + const d = 'assets/' + (data.replace(/\\/g, '/').split('assets/')[1]); + if (!d) { + return data; } - return d + return d; } } - - db.customBackground = checkParge(db.customBackground) - db.userIcon = checkParge(db.userIcon) - - for(let i=0;i= 2){ - db.characters[i].emotionImages[i2][1] = checkParge(db.characters[i].emotionImages[i2][1]) + if (db.characters[i].emotionImages) { + for (let i2 = 0; i2 < db.characters[i].emotionImages.length; i2++) { + if (db.characters[i].emotionImages[i2] && db.characters[i].emotionImages[i2].length >= 2) { + db.characters[i].emotionImages[i2][1] = checkParge(db.characters[i].emotionImages[i2][1]); } } } } - - db.formatversion = 2 - } - if(db.formatversion < 3){ - for(let i=0;i 0){ - const id = v4() - let regexModule:RisuModule = { + if (db.formatversion < 4) { + db.modules ??= []; + db.enabledModules ??= []; + // Convert global lore and global regex to modules + if (db.globalscript && db.globalscript.length > 0) { + const id = v4(); + let regexModule: RisuModule = { name: "Global Regex", description: "Converted from legacy global regex", id: id, regex: structuredClone(db.globalscript) - } - db.modules.push(regexModule) - db.enabledModules.push(id) - db.globalscript = [] + }; + db.modules.push(regexModule); + db.enabledModules.push(id); + db.globalscript = []; } - if(db.loreBook && db.loreBook.length > 0){ - const selIndex = db.loreBookPage - for(let i=0;i 0) { + const selIndex = db.loreBookPage; + for (let i = 0; i < db.loreBook.length; i++) { + const id = v4(); + let lbModule: RisuModule = { name: db.loreBook[i].name || "Unnamed Global Lorebook", description: "Converted from legacy global lorebook", id: id, lorebook: structuredClone(db.loreBook[i].data) + }; + db.modules.push(lbModule); + if (i === selIndex) { + db.enabledModules.push(id); } - db.modules.push(lbModule) - if(i === selIndex){ - db.enabledModules.push(id) - } - db.globalscript = [] + db.globalscript = []; } - db.loreBook = [] + db.loreBook = []; } - db.formatversion = 4 + db.formatversion = 4; } - if(!db.characterOrder){ - db.characterOrder = [] + if (!db.characterOrder) { + db.characterOrder = []; } - if(db.mainPrompt === oldMainPrompt){ - db.mainPrompt = defaultMainPrompt + if (db.mainPrompt === oldMainPrompt) { + db.mainPrompt = defaultMainPrompt; } - if(db.mainPrompt === oldJailbreak){ - db.mainPrompt = defaultJailbreak + if (db.mainPrompt === oldJailbreak) { + db.mainPrompt = defaultJailbreak; } - for(let i=0;i} - A promise that resolves to a boolean indicating success. + */ + async init(name = 'Binary', ext = ['bin']): Promise { + if (isTauri) { const filePath = await save({ filters: [{ - name: name, - extensions: ext + name: name, + extensions: ext }] }); - if(!filePath){ + if (!filePath) { return false } this.writer = new TauriWriter(filePath) return true } - if(Capacitor.isNativePlatform()){ + if (Capacitor.isNativePlatform()) { this.writer = new MobileWriter(name + '.' + ext[0]) return true } @@ -1235,7 +1489,14 @@ export class LocalWriter{ this.writer = writableStream.getWriter() return true } - async writeBackup(name:string,data: Uint8Array){ + + /** + * Writes backup data to the file. + * + * @param {string} name - The name of the backup. + * @param {Uint8Array} data - The data to write. + */ + async writeBackup(name: string, data: Uint8Array): Promise { const encodedName = new TextEncoder().encode(getBasename(name)) const nameLength = new Uint32Array([encodedName.byteLength]) await this.writer.write(new Uint8Array(nameLength.buffer)) @@ -1244,55 +1505,132 @@ export class LocalWriter{ await this.writer.write(new Uint8Array(dataLength.buffer)) await this.writer.write(data) } - async write(data:Uint8Array) { + + /** + * Writes data to the file. + * + * @param {Uint8Array} data - The data to write. + */ + async write(data: Uint8Array): Promise { await this.writer.write(data) } - async close(){ + + /** + * Closes the writer. + */ + async close(): Promise { await this.writer.close() } } -export class VirtualWriter{ +/** + * Class representing a virtual writer. + */ +export class VirtualWriter { buf = new AppendableBuffer() - async write(data:Uint8Array) { + + /** + * Writes data to the buffer. + * + * @param {Uint8Array} data - The data to write. + */ + async write(data: Uint8Array): Promise { this.buf.append(data) } - async close(){ + + /** + * Closes the writer. (No operation for VirtualWriter) + */ + async close(): Promise { // do nothing } } +/** + * Index for fetch operations. + * @type {number} + */ let fetchIndex = 0 -let nativeFetchData:{[key:string]:StreamedFetchChunk[]} = {} -interface StreamedFetchChunkData{ - type:'chunk', - body:string, - id:string +/** + * Stores native fetch data. + * @type {{ [key: string]: StreamedFetchChunk[] }} + */ +let nativeFetchData: { [key: string]: StreamedFetchChunk[] } = {} + +/** + * Interface representing a streamed fetch chunk data. + * @interface + */ +interface StreamedFetchChunkData { + type: 'chunk', + body: string, + id: string } -interface StreamedFetchHeaderData{ - type:'headers', - body:{[key:string]:string}, - id:string, - status:number +/** + * Interface representing a streamed fetch header data. + * @interface + */ +interface StreamedFetchHeaderData { + type: 'headers', + body: { [key: string]: string }, + id: string, + status: number } -interface StreamedFetchEndData{ - type:'end', - id:string +/** + * Interface representing a streamed fetch end data. + * @interface + */ +interface StreamedFetchEndData { + type: 'end', + id: string } -type StreamedFetchChunk = StreamedFetchChunkData|StreamedFetchHeaderData|StreamedFetchEndData +/** + * Type representing a streamed fetch chunk. + * @typedef {StreamedFetchChunkData | StreamedFetchHeaderData | StreamedFetchEndData} StreamedFetchChunk + */ +type StreamedFetchChunk = StreamedFetchChunkData | StreamedFetchHeaderData | StreamedFetchEndData + +/** + * Interface representing a streamed fetch plugin. + * @interface + */ interface StreamedFetchPlugin { - streamedFetch(options: { id: string, url:string, body:string, headers:{[key:string]:string} }): Promise<{"error":string,"success":boolean}>; - addListener(eventName: 'streamed_fetch', listenerFunc: (data:StreamedFetchChunk) => void): void; + /** + * Performs a streamed fetch operation. + * @param {Object} options - The options for the fetch operation. + * @param {string} options.id - The ID of the fetch operation. + * @param {string} options.url - The URL to fetch. + * @param {string} options.body - The body of the fetch request. + * @param {{ [key: string]: string }} options.headers - The headers of the fetch request. + * @returns {Promise<{ error: string, success: boolean }>} - The result of the fetch operation. + */ + streamedFetch(options: { id: string, url: string, body: string, headers: { [key: string]: string } }): Promise<{ "error": string, "success": boolean }>; + + /** + * Adds a listener for the specified event. + * @param {string} eventName - The name of the event. + * @param {(data: StreamedFetchChunk) => void} listenerFunc - The function to call when the event is triggered. + */ + addListener(eventName: 'streamed_fetch', listenerFunc: (data: StreamedFetchChunk) => void): void; } +/** + * Indicates whether streamed fetch listening is active. + * @type {boolean} + */ let streamedFetchListening = false -let capStreamedFetch:StreamedFetchPlugin|undefined -if(isTauri){ +/** + * The streamed fetch plugin instance. + * @type {StreamedFetchPlugin | undefined} + */ +let capStreamedFetch: StreamedFetchPlugin | undefined + +if (isTauri) { listen('streamed_fetch', (event) => { try { const parsed = JSON.parse(event.payload as string) @@ -1305,7 +1643,8 @@ if(isTauri){ streamedFetchListening = true }) } -if(Capacitor.isNativePlatform()){ + +if (Capacitor.isNativePlatform()) { capStreamedFetch = registerPlugin('CapacitorHttp', CapacitorHttp) capStreamedFetch.addListener('streamed_fetch', (data) => { @@ -1318,37 +1657,71 @@ if(Capacitor.isNativePlatform()){ streamedFetchListening = true } -export class AppendableBuffer{ - buffer:Uint8Array - deapended:number = 0 - constructor(){ +/** + * A class to manage a buffer that can be appended to and deappended from. + */ +export class AppendableBuffer { + buffer: Uint8Array + deapended: number = 0 + + /** + * Creates an instance of AppendableBuffer. + */ + constructor() { this.buffer = new Uint8Array(0) } - append(data:Uint8Array){ + + /** + * Appends data to the buffer. + * @param {Uint8Array} data - The data to append. + */ + append(data: Uint8Array) { const newBuffer = new Uint8Array(this.buffer.length + data.length) newBuffer.set(this.buffer, 0) newBuffer.set(data, this.buffer.length) this.buffer = newBuffer } - deappend(length:number){ + + /** + * Deappends a specified length from the buffer. + * @param {number} length - The length to deappend. + */ + deappend(length: number) { this.buffer = this.buffer.slice(length) this.deapended += length } - slice(start:number, end:number){ + + /** + * Slices the buffer from start to end. + * @param {number} start - The start index. + * @param {number} end - The end index. + * @returns {Uint8Array} - The sliced buffer. + */ + slice(start: number, end: number) { return this.buffer.slice(start - this.deapended, end - this.deapended) } - length(){ + + /** + * Gets the total length of the buffer including deappended length. + * @returns {number} - The total length. + */ + length() { return this.buffer.length + this.deapended } - } -const pipeFetchLog = (fetchLogIndex:number, readableStream:ReadableStream) => { +/** + * Pipes the fetch log to a readable stream. + * @param {number} fetchLogIndex - The index of the fetch log. + * @param {ReadableStream} readableStream - The readable stream to pipe. + * @returns {ReadableStream} - The new readable stream. + */ +const pipeFetchLog = (fetchLogIndex: number, readableStream: ReadableStream) => { let textDecoderBuffer = new AppendableBuffer() let textDecoderPointer = 0 const textDecoder = TextDecoderStream ? (new TextDecoderStream()) : new TransformStream({ transform(chunk, controller) { - try{ + try { textDecoderBuffer.append(chunk) const decoded = new TextDecoder('utf-8', { fatal: true @@ -1356,8 +1729,7 @@ const pipeFetchLog = (fetchLogIndex:number, readableStream:ReadableStream} - A promise that resolves to an object containing the response body, headers, and status. + * @returns {ReadableStream} body - The response body as a readable stream. + * @returns {Headers} headers - The response headers. + * @returns {number} status - The response status code. + * @throws {Error} - Throws an error if the request is aborted or if there is an error in the response. + */ export async function fetchNative(url:string, arg:{ body:string, headers?:{[key:string]:string}, @@ -1530,24 +1918,46 @@ export async function fetchNative(url:string, arg:{ } } +/** + * Converts a ReadableStream of Uint8Array to a text string. + * + * @param {ReadableStream} stream - The readable stream to convert. + * @returns {Promise} A promise that resolves to the text content of the stream. + */ export function textifyReadableStream(stream:ReadableStream){ return new Response(stream).text() } +/** + * Toggles the fullscreen mode of the document. + * If the document is currently in fullscreen mode, it exits fullscreen. + * If the document is not in fullscreen mode, it requests fullscreen with navigation UI hidden. + */ export function toggleFullscreen(){ - const fullscreenElement = document.fullscreenElement fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen({ navigationUI: "hide" }) } +/** + * Removes non-Latin characters from a string, replaces multiple spaces with a single space, and trims the string. + * + * @param {string} data - The input string to be processed. + * @returns {string} The processed string with non-Latin characters removed, multiple spaces replaced by a single space, and trimmed. + */ export function trimNonLatin(data:string){ return data .replace(/[^\x00-\x7F]/g, "") .replace(/ +/g, ' ') .trim() } +/** + * Updates the height mode of the document based on the value stored in the database. + * + * The height mode can be one of the following values: 'auto', 'vh', 'dvh', 'lvh', 'svh', or 'percent'. + * The corresponding CSS variable '--risu-height-size' is set accordingly. + */ export function updateHeightMode(){ const db = get(DataBase) const root = document.querySelector(':root') as HTMLElement; @@ -1573,16 +1983,41 @@ export function updateHeightMode(){ } } +/** + * A class that provides a blank writer implementation. + * + * This class is used to provide a no-op implementation of a writer, making it compatible with other writer interfaces. + */ export class BlankWriter{ constructor(){ } + + /** + * Initializes the writer. + * + * This method does nothing and is provided for compatibility with other writer interfaces. + */ async init(){ //do nothing, just to make compatible with other writer - } + + /** + * Writes data to the writer. + * + * This method does nothing and is provided for compatibility with other writer interfaces. + * + * @param {string} key - The key associated with the data. + * @param {Uint8Array|string} data - The data to be written. + */ async write(key:string,data:Uint8Array|string){ //do nothing, just to make compatible with other writer } + + /** + * Ends the writing process. + * + * This method does nothing and is provided for compatibility with other writer interfaces. + */ async end(){ //do nothing, just to make compatible with other writer }